From 9e0b5ae62434190a512a7b8dc9527599609a3ee6 Mon Sep 17 00:00:00 2001 From: Luke Dunscombe Date: Sun, 15 Sep 2024 11:21:37 -0500 Subject: [PATCH 1/3] Adding LRU cache to make Range lookups much faster --- benchmarking/range.js | 21 ++++++++++ benchmarking/vlookup_large_range.xlsx | Bin 0 -> 47885 bytes src/LRUCache.js | 34 ++++++++++++++++ src/Range.js | 24 ++++++++++- src/index.js | 3 ++ test/1-basic-test.js | 7 +++- test/9-lru-cache.js | 56 ++++++++++++++++++++++++++ 7 files changed, 141 insertions(+), 4 deletions(-) create mode 100644 benchmarking/range.js create mode 100644 benchmarking/vlookup_large_range.xlsx create mode 100644 src/LRUCache.js create mode 100644 test/9-lru-cache.js diff --git a/benchmarking/range.js b/benchmarking/range.js new file mode 100644 index 0000000..c3cce04 --- /dev/null +++ b/benchmarking/range.js @@ -0,0 +1,21 @@ +/* +Performance benchmarking script for Range.js + */ + +const XLSX = require("xlsx"); +const XLSX_CALC = require("../src"); +const workbook = XLSX.readFile(`${__dirname}/vlookup_large_range.xlsx`); + +const times = [] +const n = 100; + +for (let i = 0; i < n; i++) { + const t0 = performance.now(); + XLSX_CALC(workbook); + const t1 = performance.now(); + const previous = t1 - t0; + times.push(previous) +} + +const average = (times.reduce((sum, t) => sum + t, 0)) / n; +console.log(`Average time for ${n} executions: ${average}`) diff --git a/benchmarking/vlookup_large_range.xlsx b/benchmarking/vlookup_large_range.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..368d90badfafebbe35a229c06f69c020967335fe GIT binary patch literal 47885 zcmeEs=R2Hj*DpeXRAtnJ1fz^DL<>@Mq8p4(^cF3M7J{h3U__#K#^}BGUV;olMD$J& zz3tO|Kl?b|z26V}4|ul^S5_afW}WM|)_JKYT*D{FxsF4KgM;%3Cz-?6!3Gxx=NH37xuD`s443b?Hyf=I~NZRKcfVi8r50gQSgS zpK4F$=~+rgs&;weAK_suFyXRG7&r0xv)Ja!o*&cu=cs|Zei*Hu zfJ12ol8{&j%*i5>{sD2XBCobhq+mdcd(O+cy{Z=8?KpX|a(~-LFT6-@8~;g~P2nn# zNYtfVmJ(sr*R&$)?RoVQ!usvTJz|^p6Q3C{QjF#2OXBk>{?R#RPkCfL8<~E#k*xY& zQy%IgK`KT*>TJNhU%Me!K2$@2`(5`IKlO)eXWzGGqguN?UUBlhAyKMbKFf2UZ1Kn> zy7R~P{WghRnX~eXd+9-=QEvrKd!n;1=g3}qHnFNa`1N$!Vw4+vy)G}W;i&u{HCmy` z!}u3y6hR5z1T|{tWM=Ek&2{zs|F!u)xFG+_f4uz#xoq7d z4WIXNKkrsVWH8ZAH?iEmr$O`Knv8FQ&sp!(^wWs7?nei6`~_j86Gzt@ zFlHx|v}3_+JIPz@KK39%M&5(wXZ0Sb`s- z^OepolcPdh>BzF=BkMmXzDv;2$6dmT3ue7 zOPX8~&2=GfERUO1jWeH|&gmt{NyQnY8O_XJY8nyyMwsX0ux8Eo44 zg77p?N4wLmE!0)v2aAvSduYg|I&<5w* z+cX3D8lrn^yGVg@(Rtdj*51&tAbF}#mhX1>8B$t&%wK5|sOoalG|`cSenJEvir|`q zq{)Z3Z59a`v|mBLZTDJ_zOsDfL#+3*qjrW$*x?Dzjy5*Vi~5->rsqRn4(jHK_J=9a zIZIL8$eX_YX}mcxMyo5i*}VkYNAk(I{4{d($I3hm3oi-ZcJT_`?veHv(}I1x0dGBL zVn2Mp*yu-c)@O;IDPReG|J$2%K={q${jfbv&9XYxTAFFw#t~_={IQBJZ}~W3J)tUx zM%`5#Z2>ESs)pEN`9HT#MQVk*kEJaMbcY*J3io#L>DQQ_aE8Z02~7kxzxh}QLM+SrAFd?ntvcGB-Dnxnlfr#YC7nNiI`jy)li{y%?f4!D z(-KPj<(Fr-IvPW{HR%dIKB2_*aF6gFae(H$-Pz zsAsPW=QMB}603Ymvwv9|i7H&RM?4Z*LC-~Wvj4igg}wZ)XfqSnwpLh&o~ZO34W+TJ zJI&`jdh(rf=DMFH_vnOb#%8g*A*A%FcDS=|@md~!C-bA;d)6;_Z|-Zwe=ZbYjGpbX zvK4AfqVb{d3{ohLv>R9E=%b}dbBhhqh2-9D7`qeq25pbP8?RdX_;CFT;dkn}(uXfe zbL`}qQ@yaagTz~w6vtEj@pGRVS7b(Id@sgwRmNx~-G6t_gdjE~Vwo=a$8{ynuHy3q ztD{OYnv$f?dGlFvG`EG-4MTrlR&#%RN0_Y~Wn!TADBgd`=I9OQ1T6hQu&oQ}Q6mej zuulMIs-*?fV_Uv9Y4V!NS*CN%ZY*9jWnbt$=&&96+e6q zcxq@^4r)$SB3l?#9cQ*L1lx4~G@7%}Sh%s!bQ%`LkF!h~Wc7VK-O!jnc8%>kb0%V_cJI^ht!@!FV!;Ox7-YP+k=EG{A?Y{cFe-kUDXmUf>dtuu~O{Awe z%D$9Ww)K!Mq;AUB4RgdqPh$ajEl2EM@Yc-KhG4s+Z-6nl*;2ta2}*E{U}AY(t4_;0 zdOpYKi+FIZ$p^f)pAK2KCRIK@auoX^l$W?2Wx8tfBSYNel~YXi-H{=IhZ3XYAJzOM zVyhm8rbP~3+SKJ+^NWd|4@WQFq~QDWt&>2~uaO!4N0%jc&Kg>o42My7C|kLOKC8hGf#F=#*Pe-ueqyptzi>tXXfpgUw8I^$(TtI$XAN zmrX27fYkr@@6d=Shm&4B4o)2}J`NSI_)iaTwlp(yapwN#AKt5GkQnnSY>MYz(01U( zjf}dC3Ay`I?K-7BL0o@TCbzb9P+cX#;(Bt&>3^HtHlj9Oa2Y=jD#`Tt<(4_TF`N6k z_{Ms`FhR)mm!$8PvPs{mN5#*q^FaOf6txs-ZW9RmG2Y!PGBm$oT2+Yq2bZ**U3T94 ztNNPzwa^+rtLO9bb_FJMzMDa{V|eHjCaBfZ`^*}=X*~&1Q=U}Wg#;htWy&|zjD11uyOzeJ^3?a2rKt{E$(AX<`BNdpeO{l8FhvR9 zj7tw%>hGf?HMIigBqWlo{P&=%=8%&7wSPN@FoBX}x&1JikvcKt6pbUP3n{K)1q?zp-`0J~PN`CXHEb;m5NBPy_S?b0= z!^9G+B+`u!bH+RFL02{ROudE;4_{rhU57ds^Y6aWXnam~^cj}>L+K=C>Cf%<>>Z)6 zzl~j{W3_T1LZ9ry;n#V4&u(1h{GY_fCjI!tF8o%?H%pa1RY^|xNj`B@?A}vlhd?J> zA8T6u+jo7pyZf9+N)CSXtk^aOO`k>~bG6wD6|T=OOI)5uN?dpxO-cBk?l1eEb@^T{ z(l?#)U#@cdUMxHKUOFE&T|AkgxoEmfaJW1;ow?Zby?c4IcPMdjF=Xg7`GwQxWPf=^ zB6OS{D=~TN@;s%@_tdb-{`_qG5-6H3#LtGl&0AE3ZM)Q+Mt!01kG(#M~?=(L1TdS>#;0tI87Dx!m6W+dy@D2(e^ z)+`b3I4lFZRuS4Ku11}wsdp6JtAd#uiPY$Vab3u|7R;^vLtb8yCs!nn*zQnVWt(=Z;JB4ZHkK- z|6e5gl|h{d+{jp~iF00-0!gUJ;waFklE*!*etK9zmT(>wRa9n zxOg?e124V{Me&LwwXd&fnYGG~@h|Fky;DluEbg2n zbKN*$y=ZwYU|ZSO4&I_&Io{NSeqXQ{_5<}vSp{{RKGAu!Pg%A(N87(&Gz#nPz1SZ& zg;-X234dKbx}lrEEpWG*Z=8Au_NP~G`5qc4e{rge^W4Wjx4-Jpahj5rI5yTBc7v6bs~Wl$)uG#9aA zLhSvheEY1D;&IZS0;2H)#Z|qS!f7Xu!jSO`TQ`TZ1Q*g<6BK(t^ek#q?Mf0?@$Fyz z3@VbnK~T?GW$w%wJX29^6#rQB{Sh?G%QyQHQF`5Rs&ALY@%BRHU31-&27KpJiHq#5 zjs8#(0vC*to3QT6v+J%HBZs51GBLWhuJ4VCHMhCfWQYlMTUvOSMOb&*#)y}6Js+Qo zR^Hi7H#xEoQ@HM_TJn>Vk^Zk#%b-bi3ge$J|LNJf;XFMYD#&S{j$__49@w2AaTnG2fZW&e62%|ouL zw-zGM8=id~e_HklaA%w>Y$~%}UOPc~w#65C-)s6H^?>G2m6+jRW%=EE)eZLx?azxP z-#oLG$qKpUbF1jUkG^rU;e2qWxwDmcZJ~YEZ9>en?U_&x8}MH>UZ!rO3hSpAlqb#F zmjU35>#;d1M0X+CXP9r%xhJI@Wss>Y-o$9?bW69^?xN)S8oar=<3yf!#vov^Ce$#~DQHz)I4d8s23FQq?> zoo z6X{6==Vg8m9Fx4)g-hVQQJ9x`ICxAF1$0Y^^td9+GO6|>kpAr5Yh@i)9s@Y@25pwgv$6xM@9BUptA!*hX0!BBNWu7_Ao)K}c7Wpi7pOInZEch1C1^_l~pV7$joNz@`2G?CWrLze~I^~4- zJsRWpbvUO;SgE#b|vz$KLpxcI4Gx#V8C1n{MND|iM&hnb6o%k){oRj`v<1H}1!TwfsCS56?k z86Zd>ivWAKH5T}ypRof8d$BQp*c<8r27T$W;PF^<+$T9YsRLFNj*q_({7N*(70S@T z8MLAZcK$-JkZA5FoTKv|u(@BqbMUP2-@GR>faeA;dzOslC|>5j`B!)V&j((HlZxed zXen@WLU;gi?I~L4;gB`Ox3Vt;2h0LD9|8|gV3LC6_+}vxb_6_pK>*;vu)-g93uprX zn}+2u0s&Su<8j?PTmLx2DNC-hnRHFi)cAbx`DA>UcHK;Aapaov%~AbEPQ{y}9wR|J zg8^^lD(c6g>rwqRpP(q}Z8-_?P2Z@;A!a}3^h|2JWz}>NYc>wOp(R2cu9wc!-Jg4H zX5@d)VBNc{_vbu2YX$!Ll?<-ZQ?K8d%5l^aeDw5jvI;u|WrayW)dODaJ_ePoz53$J zx}&~88KX;A)}^D9LY&UWJ?GMWAeD%hZfn@i#%f(dlGDt($Dq`rx&(xo{b51`atV}< zwN$?1j7qrbHWIedBb*k`LFN+ia;~(>oPu-Y&&E82#nD;A8Gsg~Xdfas(GN6XDyeJago4Dh@>j=Ok;xg-D9n9PwCr_S27w3 zAzN{t!#^t!!hOcB^z2(GS#jAM{+~o4%=2UjOR7Z&(Z;J#vS+~K0GNogE2R#FlI;MG z3lIQ!NP>9OKud@b@_WIq)C>ZAL%+rQ6Rs6;`JT3DtqW(czo&MmBW^ozr&#Q+l(q9ZQp#*)nN;nksx@jq& z*T-rzZ)u(LHlVbB9p5%>_;HLX**a*9(RL^^DX1%_r&{;E85!Hpx+@*w zU&zX1Em@!aov>I~0MAY-BKWH+QElZAHd>ZXmFpSrJD!yNt&;;i8L6$WKG6m!8~c4;XINF zAUgsc;%Mo1;Bf?4JPZv9i1!-AdsuFbPYeRQL`y#_BEH`Ic!~ewntgvB{k7^znrdq2 zHMct>;llg6A4<6I6ir;B1h29f^J{Rj^>238uSMpP>VdR&z7L{E?Tgh*Z4(b0mh+=> z`jq|oy$jtsBMyf%HMM=_5GkTU>UB;{5XMbK zj5nG4-LmRag_8Oq%1)_tDzqf;^w@q5MUG^gw|96L<5d+lJBc1!x77=&-TOZIbur@^ z=ykF!DoY*Fc# zBAHra2jG%3_SG@-citj4+CL{8c;jH5fQf9yPY!wlb>`+%*#`N*vCJQQ=vXeLo;fYU%t*@@F`spWw1A&Y%$Kd*@I1bBT<&!ppKogTkb# zfL&vLKy*t)1}|VRx;e>}Dazsj(T1=Lp7&t%`xI9u9Lonpga{eDw`0*b$^U9sgk=yP z8suc~f`JE+H3N^oIx-ACfCV1+EFXjripVg07>veAb7fKi0jNNLcbZU~WYydqL#+&z zbE@WqjvODGW^?lV8Jf@3fizWPqxbyADe+wYaQcC1j+|A@DK{_$FH0s_mLq2LWyoT+ zLw1;V@{5OM3-b<|DV73S!VBKXKIrYTi_eJ)9E|T;w5Ju+LW-DBytU>9qrtcJL3cq7Y&B19Z z4DuK=rn8ebAS5UbY0lavPZ2RWom0ovspJ-iXqy>$!Jf&_u-Pd2DJ^y6_$T#;DHA+R zpiHT~?<+p9-lA+Hv8wK;4yQ~ZPDa>!!Gf(l_VXUX=I7M?z}cw`5(lEHRCtZ#_?J}w zUHdZnnCsQNIyWni_EWC)D zg;Vex2Xo#h?y>PKdl7#FtZR%o!Sy6%+!xF0zXwmqyUWB04ic69#Fy2Zhfc_G%Ebxr zG0M22>*_dwz0&?IQ1)wJOF>+dTvj&%vfn^oxDmHkFxoM|a=W(Y$3|6cKwmu7{Q1wVS?| zTSKSwsOA^O8FOrZ_&0)d9-}C2T&7dKD^tfkkGE>SoE(}U} zkKy=5W4%_7E9T&plh}tjv0WGLY5p37-1BI30QYA0#)`s8Y3hpRE{dR|h_sr6)!+5L z1K-V1W2jT!GpQ0}4F~I|xLC92(FN@sH$%B>zt4qcoc0BNXNTFHO~%jgw&3i2o>nWV@Mj1zMkKtWJJXf-g_T*j zhSDSziGWBYbKF>0a2ABm$tpsz*+pKO4-*spirow^r;IOc)QWni+(23!^O;1&#yp@D zMjXw*7Sy!gInC^pZb{L{JW2MlOz+8XGR_x*$K_hL8QnX6*V=_lY27)*eB3Hr+RUrl z^wU`+qKftrNlX^Yt3?Y_Yh6Sl0ndoiu6jF=W%IoHWIBRyv* z3GF~HOzlcF$Q3GQdqqAaP;z~HuR2Jdod<2W&KjIx*05;JNYF8aY}iOU(rJyqA{*gG zmE_DmIx1%z$9?{D7IJzNKGIJ?IOTN<{O`c8z~=>1Qd30JgJp;FMAk1I zf?lVCGbiUU&jEjsseC*9x27lUq5dW&9$8+rVYkx&sgB$of(Ut^{I1VRImnJw8r7?M zYF+rzPO)5KOmE|lq%+B9^`6DeBr6Ud$HJOhLxBs69(u_2g?)MF=m47@x5e{m*}{*+ zzvQ{TUT*P^>2^l-XWp>|L;O!EYs zFOGn$GA`K6L+5r~`^P7haZwd?`F7CP3GqoE<#gD2D&sy^;F;oCUQ? zDN&}j?5knKrn+)^8dOvAOX;l>a5YMZdS%?bN+%255MvFEDH^YWa|Wrv5)4y`rqC29 z&&uHRGS1Q!TTq@;pgb#sGs-x3g{*WlM}JW4xKv6pVC0fQY$RtHxVmT+nnr)Z{Vu}GZSb6;st+SP#LNoB;14h6H6enleRaD?7 z4#KE_Fr|}((JoJ((7m%T1&OT1miu^`tk@RJB;$zC(pG7iGPq+q-r0rdx|SH|`tQeu zc|V;z1#8yRhuV8h169|nPE(E#9GWgSXD$x^L`wKx&Tn6yE#_c;n7He$N3uLETH2KR zU4mp*|LvN&YT_)`_|hpMjn2B@Mz>Aj4R=efeAo12pG)S)Y~#L15~F$sTY9^t$0fHH zm&v}lmrZAtCkuIA%J*X13I!-5>!S562C9(jCPPUJwMuUz>%|f4&v%6>dl%&0zgyRQ zOJ*8}&RF#9NA*+oxNgs(7d&^dO>#b|k=vn9_3a*|IB1L5P|8rm1{O(jGAHa4!N0CN zx)~Y@wc{n?911ghkai=(q)SajgTt>co)z{slrB6JYKr;TL)%PR!@&)E>qp5CLrs9M zK`7KBffAzd4dJI+!i0fPK@`3tS~5eS9>zkhv+ToRco`-{h8jTpJH0hmn}HG{-9cKL z`&q5zH4oSCT3*LEa_Ki4xibebS0Z?XyycY_=wEsgFzbfcOik7G3aEx*$Yj^GFHMiE zH$4U)S@TU94(Xn-S@TIuTNd8nlrK~^5*exgnbSFL`1jR@WbNonIwb*0tjMUI_+ZV# zJJy-N1u|U1-!$hZ`%}zNwH#XlRh>mKgcMD^FsiEKCB16R50Qn5FM0g+zDB zkufF>wg=}t<|Vay6|M+cZQNjcch+NGf52J-HgT+-FjIh+;Xm5gv3A+T%n%Y)3zN)| zFH|Mv%+g$#5TehW?bmf^V}Ow@f{aWkDTL^sr~$KCPyxs=85$?Rm9c z*MSXrCov&IQ!x?4UGE+Ts6-Sd#jX3s?U*xz-HXRD^qB~^OELMz*zF}bCo#Dlon8Io za&F}M%WnqJcLl@Z>V`XK&P^&glE-$HZ^s-;aO9PwT57jajP~Ul%jr^(-xdvz@AgkO zmQhpT=kT);qq;5n{BF!mlN>bz{*7A%(rtsPDgqn{Hro?FiBPc>cp1ha;OwS(8&SoR zXDrAnNQ3$vctLer;ct2?2n!0ejTCfX8dSGbM{E$(FHOb#IR|w(afY#+u?8@^Gf(sD z%fRB$;93Nw<8X1yD2>%{;BfJ4Uamc3I~|{iv4CMKkX#y&+#rzL+l)y{ZNWLCJfv0v zAi1|0&E)7U-5YcC)Hreda z;+F57wusvxxj{t#M8)##0>AelOswY1;LOqTonWC?|0Fl}pX8?glU#q0T;jVeXyjFL z*Gv#aoGl}j0r~QXB8&9DahcvvxlU-2W64|(kj!5z#`aJ?@?Xx3?D@HuKNfq(@%JoN z?P{N-=qZ}IX^t>)BRlo(+C7c0%IK2UlWk-qJ6Gt@WGZAD|FBskFn;5?LZK)%$nZDk z{b*&e$YX>2{iBl`J^TD9nI2cGUAf&-_a_&nOJxmC-Sa~91z|Tnb$@G^>j;uz+MXr4 zr*_Va6*VJ!?YYVS#M!62y7?yuY9Y%3@<`!tX)56lg!n zoI@Uo0yJFum|WwL!o?&l-wPB@<&}XEJI@P%!u;hPDKNA-uifdO^~$UvDGlJO<$3Y8 zOFTRZ8an(D@Vv0>0FEEG5`d$NbRbEoMg1mtQW~U7ca!#22cx5)A*w2YwknlD2)z#J zZg_#vXS>yiiOgXlYDbqB(sV3=;VvSN7?YK{Wiugsd{%+s8i0*g>L$#F@TCLx4dM}F z6udk5KUx*Q#>KplmQ0F@mS=z6K0-`gQ~rY42skCcySuZ=N?U&5Q*X5yt8<&sreS5{U|p>RW2_U84e}3Rc`P{_GKEe+!}GT~Ppsg*~b2UZ?qywBYRF zdx-2?k&vzD%(!H&`NeX;be#P6lA}i4e3Rh#p`MdtqkQ8)(wANPnM{P)AF*BL*L_X~ zy~P1zTtOVHjd8Z#tGZuTW))^cB>i?U3#*2IG41SqVGc>Z2_hVz#+Z+5x^mh}X7nZu zW5nSO`oB4;X5_mOcVimkz6Ew_%*d~$Hy^>B0CcWG*5~px#%%!TjLGDNWqYw(*)H1C zo9u$KxC+^QrBm#LT6?kQg@>-`hM19qK;*iCKRyak>6n7E+yNSaX+5TJbZL~LWAP4i z5t(5~9;}kh+~?%8@($AgZ2VvqVfH>}I$+-*W*DNzs)GNcRRL^VOrw-!l0&rox^DZ( z2}MnLBh&~uC5%;d11G=Hqp{T2siVgLdo=>=0ISC%%Y((Q+A3pwa9j3-f?`Uxa7cPqnI}Vw`iI3 zJl;;R*Q{Y)rFQfq{DGDD&Mh0Z@rqKi0VxM1C?FmW^aaWI=;^c9MIF?c_{JmAzIs-L zLpdlt!A+3XWxbyaDG2tu80+)hE$FzqC>_%bTjdkFfn&wH<-W*++K2n$Ue%h4wi{Br zyUpc0eca}?Z;peOKKRHTZb#>Mb1s-r|1D^EMiGlG4B%YKDl46=2%$3>(YeiY-0L4w z*GyW)!F}KwPRV}|7XW~iH`F+R@>ISZ;a5`RTu)$XNTdUrZ6mS;bAvmQqPUUZ9G*67P zbMMto3T!yqvUUXOZrc1Qa||piYUkw|SX2~zd+Y1{E^oz2%2k&Gsi#Ta(Xuv%uSX^+ z^8m}0amd&tWkyD1avBu!63T`K>O@OZt0KWDH z_}UQQYwonsiSdV)IPMNjw#ze7$~K1h-zO>43J#t#B_+*nccTWo7b!~@B!@Am{Zp18 z`-&^1U5hPs^gMULFRBFzc0{wAHB{oG3uE^ukvX+$S5>?w4-`L+dk4hn-k;wVv>EEV zBN#)pepSnYWOY9*4YtHhJsavaWGBOD=xjI~wk4fHlm#d>pT_8EH4Z|@_mMJ2K>7w8tG0)Z5i@pqW2?V=KfwDU?TBps+TA^%V}P}FFiXk0_g)hA9xno=4A zpw=u!C4kVthNqG^1OCBFU=k6iJAp_uQNzgQsI$+leh-yIR4~NAF|t|e>`s6+0_+z! zhA>By9nT6ID*LaNFiVqNO*juO`8^MkS8grEfk8wopy7Dg>g-5hWs9gF6-q*Y4vY!T z(Vd{#u<5_@x5kF5z$+dil^Qy?Tl2IeSVHZML1L+D15127@m450J2y3%2@N1c2D)=_ zN-$3m!S%@jgjRM{Ef`0UW2L}wRjN+e;WgECF)wj(I-c_JazEbpY)SHgKN{&Opf<-Kg7AVs6hMV=AEPc($M|5xh*S9Z4gp_5&D69+f(+hDv5 zBRPY9(=dOiM*<~*!e1~I6~8WmP!Wi>c_wNAaMYi@#i>Y@(afqnM+K2?Dg2YXB;7*V zv(m<@?L-YxCq@}nI^`IJm}^iGCIjt>Kj;fsgac1>*8tPW2R6O# z5nG5d>3ZQXn3Pvdxw~&fBm34hoxH0jzAj8&dM>;w50}AY8kw-!JXqv?z&Tory-v`X zaYt3fjfh!g6VjhR)wR;>uEDUMB?DE*7>#6*mVAp@Q|-|vr;3#CO>cGo4i%5v%Vpv` z;h~Ew061D^`ZqL52I^(#sWEq0Ap?E?=(%~k5d#&(HQtDi3dAODtLBK1fd;D@KLyne zv1em94p%7KBGrR_!kAdI*?cQJm>TV!R)f#|fc;dX2q~wNV#dgT&6euIhzEHKVEHjJ zBueY5_#^MgqyN>C$gitrI*num=kBqpS&4;eBO)2%$1?C0msOcY-;w_)r-Qx`i6j+C zk|9xES6vw8G{X?(?&UtO=v09ZJwy(coop^rj<~~{#Gbn(V;z*Q+kiTLYV)&caGS`W zqTs5IInm=NBh9`wP{!EOcXo2BSK?CjD{;F<->#jN4*lKCiy*U#Wv!29t08J(C?7kW z{(Z-h5j|ygA*5akGp0m#B4pu2FJHaK^)#*X-Fo~&Z7!%@ar_N6C;io$7Y0>#4HOEe zZa6TF4>!c=4TUwu=w1398#swkFW?@3?+J`l!wBwzp%}f)%0Rz0FfzIc)nfFG(`y<4 z_oR8h7?l^l@Z`tE#>eHklDF5pWZ#It)hpmr>Lrl>vZt?CV+2w*max^W_aNg&8DHG{ z4u03l1w`gqi8j>X5e%C<_{V=Mw#6Avl_9@2C)MApaRsz;0nqx)mycf(Cz>Hop3z#M z2n|;?hG&y@!Dqpf7G!AKA zs&Ohg3%zqapGRS;Bv*A>Ak6F1iS_lx|3*Dt-7vUFWU!9`dCj(yLjYnZApZnj#HpI! zh_z98pYH%**w;1PhRT4f6O@C(%!zy9UqLUlSdfU90_xQm$nBF!LFQ_^UPT;WI4M1?{@OMP&pTIw?tS&$jXiO+0p-mgbli=EF( zJBh7;we|FC50>NPmMWU?7TII#3_s zz@$}Fv3k83i*|^fi!nZXS{N~-c8IKoh(v*1dtrna^&U9A23B9;#rR4r+Ang*IOc$*7Z9bwiwVsj91N@Ywzo3LihY#G zZ9M%~@C#-ns9o^D6nx^ctMn(|C*rj51gjv2z=AIPBFnBv9S}pAxocVgTIHR9ESu!} z0LT@SZ`jDWzdr9cJoBySz-9Ft_jE&}703=&p1Nx>orxQRE-{-h z53s&KEFb-J&Or3M{>z(%2(e^$%a79rD@Y9fV_3K~^b4jn58g@*(X2oYvV!BntCBbe z*lJK=!LZc4S`J{sgu^V2ReysGTfYERAi!IcFqk4noc>mcz~B#8Z|l z$(lqFjgTD8rnN6m4L)WDgB%~BkG*6IyUVADgVt3|cou-A{3p{kL&rglc^C)FC{AzS&K5i`d znxs(3&P&<-@#VCKtYk^)PF+&G9kU` zr@n=LjF8wYZfp^)qZ`%{Gyfy!)KIcP?Eyhk_MDpR!?0^&4&83%Q+$87qXl;+lLprP zXzln*Q-d{;6fxtqL2So8>3xP_8FV;ap?raFV-)KnOx7GGgcVL+UFCff?u)omh2$mJup)vj4j=tJMUmvq; zn%&MIE4o6`?KVFRSbpIbS%ac=L{EaEEzW-nik8JrAr=ttn4f9Zf!m6RN+>TN#A0Id z^}(>5VIA+FuzBKMz9(3Y79$cVR}eJhK%8LHsA+rlzqh~a+5eQm*yDDcuJe-Z(06BZ zv73NFPhRvY zm7K7Ro;|L|8@R`BIs@w)WP0|eJYN{RTGQEWlmnv^-O)~{SB|ZL*ULuTJNzDzXX=@~ zJA14=>>A*nan%Zvm&tCPqs|alyr@wTtT94CAf5SX2lZ3EkhEl_Em-3P1w=ZNv@G{C zXAS_B%1^TM8%)OruAJcxp(&0S#0#zUf zb*x_Yo%RWG{YmS~9xUxAe#*x}D7|qZa8A=kgAIuHD+}cMj~mO~9~aR{zn-+*%o{W1 z;(}_e)+6=A77)9;0cN1TzMveU@%$E>D#4#_1_%$`}cVEQ<0-z%`~hf z%xXQ>82VB8BWF+LX8|!E*bfLu&ko*{QLaS9n&p z!(=H#EmZzo*r@8~T#9BFxmJ0Ha>YK#Ne zbrP?L6hy8cw16u9bHLc+%HYXSb3vXgTXBB3#Q$Y4!S+{Jx1fuZjBl% z`q*3PN7%x<=z4~kq}7H1hwel3TA^b)k1~ugU*$(`IVQI^MNg0zuU)1vNgkFWu>NQP zZ??seFD+E&YdV3+7lUk1tQq|Ksb917Guil$tzQ#<`ym4%=In0Pac@97Mz@1@jDV(s zcC5)j1JT4Fhpz&}f_4m-1JiMZ{1ecQ(d}iLLKU@<>}XBJd`-To-XsLH;>ra{V^|Bc zkL42)&@@$(?=b2JunLj00**8r=x9nPSfdQGI$DF4i~ube4q7rK4Xij|EZERskqXd~ zF`y;0J6U1nn_a9yOGbc}3-eurFM1dhjE(j>>{#Dyh(5dH=>2J^VWF1)?|U!LZ0Qr$WATVm4*v*={#1Cn_t1)+ zMfRz@VbtAqUK8A+?sZNkkoLa8p2P0ZEEkK#hq{~B%KbgWEXAIO5v7#mNOCS0VUtNK zb9oM8y%rbBC)G(&&Z9)0A|XTA-Xt-o6Ifuf)qocSl;sT2Zw_KNq@*fx0=S98#h^Zz z)*qt+;u}04rbhPuI;hNUu$9;p&26yt*g#?qVjqI{2b4ohuAF!}lsAGy zp3+n~1k6~T^7r=eFo!C)MQ_o{djyUJ<~=V~0q%bNH>)nbP8HV+Y;{bMC6pgXvSP%* z1my`PsASo!3KkQq%@93=CzznbzyziIdUP_Td38f>#pOT}-28zFN*Qc0$^PA7>PK*k zS!i9I;enW|_XH>^Pd15C!5*w?T$rZ?Z4Z+7GhY{=tb#Q-xIvVgBFzFsGEr0|C~OSq z$DjX^XVz*Kajxb9^S%_AU7umzmC^D1-#}f?s>VgQM6d5L+fR9B%(|?^wEQkWRGw=p zPGY)$04O7O2UiQX*Cn02X~x)gjnyAI+*YqcT8o99>s=`y)o=~#%NH&)>ph~kFC^`# zM%DY${EpCAM+|cAo(Lt0_P9RZp*udXW$QD#i(Gd*46Y|0jagVrAL{-%fBAR8eZjLW zNTqg{efRn21#9=0K0JvJ{ljGR4#e^$Hm&?@Fh3_coaWU+4Ei4pB&=Os^2f`cIprht zVYvMXYXJ(-NMUHwfNKv|NI3Dl+7*V57YS>zf(&LM+jDcd;#j5q9P^PisUQNytgnQQ_xOwM9 z7n(gBq7#CE=Bb+KS;x6olGc${w0f3sfz5~l*nOyh-A7h%t}~mOl|I;g#Dm>O7TA5L zF^$%O-3J%geZ+&^hXRT6y4nH|;Z|@-v7*E7$;~6G|AyPV0{iKL*RRT1g3O^d>0-G2 z>^hc3&IbNkis&g+wUsUs4v5ETuxu=VU7=R4Q=|{=6y5^DlbLrCRZKp*cAzMEB}Yb> zAf_wN;;tlcLj7w0{X>>YasrxHWRNc9s(&_UHzjko;d^PP5u(`1Nr2Kmr*P;*t|zpT&0h%;nD z5a>7WVvD7uuo;=0#Nl*M2Tbd#>eZVbk)&ntSzrZT>WCz*fb-_b$QiJ^y^GzbAkL9F ztbdb8Csy3PI?Sj5oB|yksm0{)?BNM0yb3WfNHfo4vkE88ks*4m=hF80SLK58l99^h zGP^v5=i_O=27{8*>_UFJ z((b_p`qTwc8vVQ9BNij9P%IJ@kvLo4oBn6FJ!rl%=REC^*e|;9EWSM3(@M^Mf~C^0 z5kGj#F~9$#fUL)L^=A7Vp2e-I0tH7dZ0&=sz=c}dldOf4cs;p7w51+n%Eo%FmLT2jg(^(~_xK{8=U{d*w1>UxC!ICI?#oHAuJ+39s+e_}%>K%f2huFHc z7I=+GLp)w`-SAzGBbGsOAATU1mECRsZ9$PK8OSg|Do|($r;>t|T3;7&iyAv-6TqEL zmkmS=@LNd1CQ4gbN7%%7b5t|^?eDj-srjKGWegB29V7BQZCZ#@bZBJ(PpC$H1aXe6 z;E`z(1H^2eN*9usO1r2OOf!qu7h>t@m&-0JgNfvT-D6h zVxKhW=+Jf!$54%@V|T|{)kqSc($THy*By1{$X8{3rlq507vWM2=}OUHqs;_1+Tehf z0tY+;*l5dwjW!Z&w7~%%3J!RNXl1l4{&yH@S|Y?>RwNK{QJDB*A6!O0$n|;3>q3l; zWO=WYhML!mCj7;rGB6?F_>~}^R-xG3Z>J6 z^bw>Hj+B50X(T*!BOTI$gd*LIARvu&OL)Ljt@8DFR}n%6s*Os#VM?ltj5i z`$)WwTB4$#6lkD~p;9&y5~IPE8!4@zpJWm>d;4cjBg{Ci;yr`;7gS-$!R^)!W9&^E|wsCn=^B!#Vy2| zDna-TKAa{&m}szwD*V=mx1=Ux3vTvub#Sm0T_a?IYwT1@Bbi|!V{T7!=We6dnA3cG zi_)4J!U{>^kI~+$VH{pq(3gWco4)C^|A(+i2v1{8EqCM`zoPn5nzK~mZsI_OeBLor zCG0|B-u;r^UD%&CeIlbP|Cs5AAX=WV$d;{g@AfglLNPq<^$X***Qph01 z`;xN#>};ya=nZm^ftA{J6+3G(68%>+eKu( zM(?6lOrAmBMxk|mJnrv0o>n6dzrUkW`gA^>dP?@<(Jvm=Pb2x1zhuwrF6a-3YL;8* zC6LAm5rUH9PAnc^bwkA&^jc=F4T2!h!9_nQ{*~dtt2}gci!Jnc33StH#+=@EhAWGr zO4}MH&L|F^+o%0ds_`Sn?y_uhJ>w9ijpoRr_m4HEn)Q~Y+V3Y-qaii)NuSaJ4FjV2KMAwe3j6Uyxb>Rm02uKI>`r|?^z0m;suyg6kg{wGQku{ z`Bu{8M^)I>6Ps%ZFM}UOnuNT0NYTYq2GG?@w2J+NG&Xx7jPx=5ihWkwl&$*S&?dna zZ&O!&Emn=}7G1Pnf}cBPf|+8CYI-^SAF(Fp8q~9;afYP1ALgjJsGe`7%MHQp6bFeH zz3)HC`Qcm!{c%t8DAYalm8Se8vM#RIWhv%bw7|Ov?{6etuDJY|I>*Mf9ldTHEI`zT zAM1K+mpxH$_|*JMd12F861u;&f{0y~)>3RVM%^{jePo=(lAMfjKegx4#g`n5GMm?< zO3&q(;FJd3zcm7o=pE5vH5v=30y{u;IbuPqHM#b0g29J5*$&zc$2s;sxraHej^smU z8UX^VB~*mofXM}Wy?9G<*n*QLOLFKYlwZ$=@v(vTTW^k_1S{%5Zf#DH^+r|OnFeY2S+4S!X$guT@h)Us%Sz{iHxv$opeDSJ;L8XLmnoo>Hn9 zl}))D`G<5_qi4AL)qc|ArBq-iiQH?~UpHyjQ80LDn#2{}`B!)Y3-Hcg@k-0W6;TyvMlxA;rITyfO9Uz&d!J8poRaENOhl*&B`M3i}h zjm_pdYh~rLXswZ>EQ z0`p!xrpN6@NRsQUi42XZ=o+7LOM`jDoRcl}8b!tOeCGi6Y*T`<;dMacYca}fkF7>A-hN(312nz? zXnZY3jqNeo7zR#ODmtLCq@n3!CV5{~0+S$+ZQZ-F*Gz(7LZ*zqZ2!$;J_h|{Jl3>= z|6e>tB0PGfe#YcSygaH&+7&pm+qy`3nOhgp6yrESw>rHb*l}t{sp2HS(K%SYlF{RJ z_T~#O(B#1w|5EqPvm?av`L*fy%fiIXQj}QK73KEbHDBC=L?>!RUZ*J>Zs`{JZ)Z`^ z|LCVET3Qh9-i_&Oc)>I1|M=4T<>?Ms!X9K=_J46XB$}_6vG*h-A;!Iz*>q!e{b{2f z(UuCPLt_Q?k#j6l>XQ;l8gwd@(Cg3JT;&Nq0T~7tlW~2RYD+h(T1DmSdMwBKoPXs$ zVzW_WMLWdlh*NdGv!;Mp5Dfm#QYcSOI1m@suCX9mXh%+HBvv?2zA_MpF$g)XsT@0F zj}Yb2Ju({-BOoCjg`Er6%M6XU|J8mOS-mk6yB!7;Hl)t@up}~eK7Y`AGT|{ zw!BR@zuHS;(mYv>636D5?m01eVpas+nt~krS(C^`ixud5tAYJsabQ0feZVL@c4%ER z)XPwfHe(|0PQfv~l#ut07_JX{rpH+fc1wC0NVR!kJNG=Y4pCdb}U zfvb(g^V#)?#N=^cZXyvM3W6&?FlUTpH~-qc2$qR_?l+n(eN)$)Zq`Ag&m*heG9r3X z*(wiCTd$m^#&AhDsG3Scd{fl+;~~D5=><~<=%C8%aK?0Q%4G}c#oB+`B!%A@J(8X~ zNQrnfy&Rck6cH`6!w55siWZ1kR-Cg$m9Iajy}jI$zuHZA@Lcn#4U<^28gps%_7QT8 z68qYAuZQ2l!m3G$slh`h-s6ryubd+MOix`Z)gKeL{W&UN6{-8SL?}Y~!}HP8965yd zXu&+0n{w4@tFQz;W&|~%e@^RNP25DnyclhqC=4cf1PH=`+U!wbrGeTHqg;Vw335En zQx}wEWvptuJA&1`XhJ8CZbR92XM`ep0j=<7Ul2I=7@X7OPS#vZ&t-d9)%GYh-KD|t zcaC$x%G@6KnM2DXVV6Qpm~p0+o{<}FO^3{Yp@V!Zh(r?OO{+#KHAO|+UA*qBU&@0osZ;1#Syuw;C2@naSrPOMov z=w?D2o%e>5xB_L_!mqYrC_5>r|1D0r6J%$<<$DpC_m9UM-@o+x4TC!lqY_EAdr>C? z8L&sq*3JFzDdAI#>m;xp(F$sIZ^H$0iB$b86TdfHqEaO?>{RvZitqM&wu&a}%cI%skNAzjDODNCpzi_Ud+pEm;W; znZcDEB@a8auh}4Y^F-1^dGoxHf|f5IhdyT^^o!&sd}Xj`s5pqCp44Q$bIANXqVR2B zMkRNi1rJ+ajwg*yZ{*L}PjbVFQlZ>=Z&~97Em?W+lV?x;hftJb2h0>f|2ze1tg_$w^XP+bE-095!QXyl+TRP9* zm-RBE5~X&Gv9f;;e8C22$V~oHqt{atl%Z_RwU?D|?qm!pYhfC1kUB#@(5E#P-^Q!2 z%_mU}bdU5&p`MjLc1kMxK^y(XgudkoWnk_NgeY3*W`I)8Za~8N=5avY)KS;Q2L`xa z*cm(PAp5rr?KK4Y{ zBL-d4@+{n|1PBu%1qhQ378?Q6@zzvM1PwM-WUQA|5Ee@+YAEV8aI2Q1yll5Bfu=>? z=lO++eL){pM@6E+1t0rzNSuTLsBB=C4RWjf>*DChs`MZ{ZfLy`sJRnR^Aw=wj6ltA z#_*XUBqqJak>CY6)o~<1)xD@Tg6UPB7Gj@A&-foxvUqivVmcp#Vsb34%H6jE#ULeX z%!mDNa&Rbiw>DTmorQR=Uw4(E6`xOgpZxGF{MD)31k>T+HL^C;eNS2~+bqK2=*(W< z%z_YPSFp(kN!px@Z7fySdInvzzy6rsdr%`Ea>fj@8JN*vKgw?J&Nor2F`U6VfOC8@ zT0V5F^z3RG3(-3BUfy3${*~@kvG}a#I;8>5Wlr%nc-6($urU>V@dcMMc7DxmGkBB# z&Q|Z;#)^4!IVw!w!9btW9p|+KR3sHjLD@H3ZB^DYY7e(eMO%BNy6>l|%Oh6fl!7i& z!ZH>2*fnm}+FDR$Y3#V?+kt${Gpugx<|JdoXM-tz4UGgj?&sNyoNUS|1^T@hlZ>o0 zm2K;)xCsoZo)`@p79RN@d+|=pXKywW{x&!Hk|5{OaM+gPV2>}BGgCUG{y1u|xjQq*5Br12q^OmKxXtPX%VXrfrpH=&NQ-lT7d_c4^Yan|TC`{+QwR<%J> zpv%(eTVZr{BMFU20|#fvo2|XjHibx}TzDPb5l!uF(@z<3Q=*mTeoC96=)-3ta{Kx? z;gN^unyaQ>@ULkrYA>xNOWtK|?o`Y_B-)slYqXX86`XyN)8Bx78Eo_5e7U@>+Apfo z;!ctg;^{j=%br!^g3n}x0|<;Jjf!zZSewRhu6JA%u3vR-{S@wN-6_G6n+w=5%{w;ov{NI~KT+fZ+-GCT4$3o{;8$||cAOu{r4 z>eZNf+f<}SmIV*?qH#e1;JcI)ptzLGs$iH$mK3j&@+OlztiBu7?3R_y8@*^8;$=t- zNQ}4xHsVPMeF7<2SvATnjSVWRN^_vHs%#AO#0K6$%3S+zmHBT~mF-u0S*_M=7NOzz z5IaR-J_fSn1Ksy5yPC0OKQ!9@;x`WqxDP>Lu+FMiLfK{urwmd zem)inB)T?+N=fT{8vleWkFRz}MQbD<R{!Vh)~5Qn-UTJw-Ixa)lPFk$M9mXVKR% zcW&2ZGfMh=x52=SQ40cW_K=GRX*WRW=VceGF^x>R{tAj!!n3^P?xH){PP`S=L_u%4 z=HH?3!w=9Kt-t&@32_%X^Sn&9VLG4ZvU!oMMwIw#NJAHOw!5jS8meo+&FH8aI#KYg zNE)#$n5mT==nAz{aI|PZt0UZCF}}dL4=+c$-Ab2)p`ne+>0QBRkcBa@@>jL7Xe=a= zE>4(yY_I=@Sl`hHb3cQOdmBQpmPI2VxyT~EV7?FIgh&Hq>8hcod!6;@jnQE}qG4Lu zi>~qd46HoRT`EAAG_*AtnbNr{!oVte$lPLSRu3C@rCb@zNV|D4DSPADyAbsOMbH)F z;rK(8uMCx3-d80TWUsC5=c+Ls{EHm?|LB{z^Qh|ct@tYc8)oBWvFs`&$KFygBAU3$ z!Ey2!-k;ZVU=AB@bkwp-+ZRIoE`9H~#Yn`gZ1zdc|4Cwp%_xiVf!cQQMa=!DVK9NU zHq9pFGn0kiByhgiHX*RTVb)EJVN&mM@NMV$y#`XW13N_5jg=6~`{GTd)hHhK{oD7V zi0#_ml*J5I?UF6;qY_eQI9k4!BLuvDd9vTRnLMZ7S`%^1(iTPK_1andEJTh7l{8`O zwTM2QHplzYMzUm@S|WfLIP0D-K8muvSia+ApgxPm`(Z zc4tMjpD9)~2+6d;R{+xQO>!vxhV0$UO>&OT&7+k6Ea87kzL~oJt%Uy#XgJeIwKWZW z!2&aVff69K{S~RpGz4m`X&Pi_zabGnNzW?cGs`)$TWBOL<<@B=TjFCbVvuO-_KWu2 z*jBPHBT%;0ibMK|b7F>mhN43Z*)Cz=3#a8fGHCu7BBDlrxOHRK>kN&_8m9Mu8QAR( zMZ)=1mZ=EJ_h1zcfe|gTl!Hi>=kz`&5_ulPj%O>K#R5e#f{z;ocwx90i{q&Z;p#Yw zBc)$2y26`8IE?n&0`6IpRF&vnKA7$Wn(=~ld!hF*2U zH}kXmOL2|)dq0`kg6W<=%G5zdv!w85E;?%q-ekoaRZhL0HgCD*{aD7FvYkYoJCU&C zRak3@^DXx;ye_xgyYXxvqnn3?@@86q*2(qR`5-VA-GZ|H!4O4sW&f)kjh(rhFU=9) zh85hIw3XbM7U0O{dDqJBjkBG-?;p$V5}8iR3$7(hF<87;05Uqu$21Cp#NfVmlq(PU zKc*9X{;FYf6YS}Ji?m(1R|wTw1Xr~du%Ol=0AM!;{tv;{a;05Q4grxG`9CPF_i{jn zodYuLI}#ADUsFMX-WuId!GXCT=f{5W)86U3)9LUx41o)I#uC76Bo&2xGZIzTXB!>U zdC#xhDEGH?kw=4Hm}v6jMG4!koV$FFLXrOcDDxTgjzR1W6WR=_h)+_Nwrh2cmc34! zodf9vGat-7d6JEY+VrT2m!r`41yt2W7s7&THHs*`uv&YT$*Bx*)V_xXh*r$2u?sSh zvG4Q9R^hNMtFZC0t9fw|{azh5J+zx5n9fKD@mdIR|E^kALh{1v{GYvelLsuzm2G#_Bohc_y)ZtNIJW|RJ0k@b zSWYY3?yBtzC~ozNgQzaJP~TCnzrb?;kXh9WtYu9xingyXd$zhok5+R1CP;zffIA6S zwbl594YIp%WZ0&LKR;{y0B#W@B;M7u<)&?C6N;Z62$0_+NH?%}&6H|azx3((8A1FK z^Wkfdp3xZky~fWLX9_E`1_3+x%DyVIt|l*!gZ*>YOr<+6Pg+4sGneK1Nc@(aq5~fT zcQPK4N_S|UwDibO*6K!icW~7It+b}tXkJ4iz_*V(fF7DU#BKMhOidVK`yGk{%fcsS zDPw+5-gKnb7Ct$@cYGCCwM@>w*umM#rSjH|7h1G)fF%fh5^w zA*pKhQd>wQ9PkeN0*WjxdYjP0^gx$*{aqHysy4WqWFo_kM%^MH_X$C$L1n*pSg*!T z=$vf_F>IzrYnh(sS0{Q7t#-f9uN;+=V_1aJBs; zu(!-C!pqd%7XuzWQPyYP&M`ip$RndA0=QIAFJrtN? z7$3vq9Wr8O7F$AFc5p!%0&oTjAU zcbOqkw!)Q+3xq<9-eTM~~v)@$Mhr*g)NP{= zEdy*b`55q%4?1#L;6w3sbpM@|W$k==W)Sl0ipkSYpeSvJeU+1oUE%)M6MpP6KkY}q zIfV>;v*eeNXIzDp{LcEQv;Fe$pBRH3?gKI3k#%Vc--T5Ic;hQBw`Jbt{jNG?&^4E9 zk?b&|jwTE~eVD;l%&Fl^vW?Y9%8=pPUe4J-GfInj>S(#|EcA(tk_P#Q2(#W=lAUr0 zh+ijI0x`r{NJp>Oe|x9%Cgvh~0jwXfl2J`8#@1N==r|lXp7prFZQRYzv^HpQH|1QW zmyYD-%N1;M{!9S^hs|G&hbKH_L%02D_irRX4b|L-g4cz6V%_P%nuRrG`yKUwBxvoV zzr^{5wigiEOj!%y(yiTviEmi6? zYK{~046wBj4NAY$sG&fOpKJ5yjPMKCT=-uLmw#FC_>a=9HIl_LH56|pPx^8URhF$b z_8u%r$Na;}B@pbK9j<2vcf_t@G$jPE)K!eml7k>0%DIyBD{>!oMI$IFP^U=Bi#n9V zpIpjyw)aVw<8&sL_u-A5G-uogKX1Y^?!X%pXJGrMU7THTn!GyCUlBR3ADE5ZSEPX7jKa$F1G6ZC)eKH+LY}f{rD7%Gi=&+^$s@xCeMNeL*T(|bXJ&|=mldAd28CnZT62r;s4Kl}raHRqfB2;qi zW&PU9IX6ee$>%_Qjm1gLA$Kx5)7i7>Tz`eclpx zLZSCzrF)vg4%e#bk{k>5gvHb4h@;y8J7=)U zNP13?WUx3bvUaMw<@dhbmeShDdks&{eLIcToqVX)Gi;dMWy9_3pv!ie6li%&i=f+v z1$sA^?g5P!N4G(4Q}dhZ6U z9C4;#)krAv7HU*|`W-gEeuV6UDbDaQz>@LURqnpaTUOA@&Sha4iN6L@e8I=?FFId} zJbVnq$p^O>U?CcZDaLVkD0=_CC&IlkP;X7sc9l$7TqRT7S7ar*B5MQ`erc|%D4Z*@ zhJa=X_xbd~F_{G)|7#WQk+ik@vW6D9*f&aJ21_$6m-jJn7=N*fGZjS14vjH`im7~k zxSTg4nqf@ZdLV{M^%IuKUU`}Ts@_Tx)ie^;VDSaK0~Y!myu;Yp>TdB;fH~wcuy(@H zz!~gc^y>`#nszg$%G>md*Ebcage8X5A3QIYb;8^Cm+8_g&3_J-&%(rB+nUyKyf_Se zRwe%(d^3TAfD>via<EMDS_NdsWSvo zg*Q|kF+fOmg*u#ZDYuO=*#sbM?^X!_@zxOo^u%4YM5AKCmxOkzz=uDwFomy93HlJL z`XPN*ft;m0|``amUypOb>1>)aYca^ z7u1>t8tdEO29~3a*P+Vj+#OO~UkJ!7aY%L5Ga%J1P=k`_v3#fD&Cm^&{-&p_Y2%N>lJgG^AAI*HTDJRS+?N~s8f`8{2VCmTB2%R^9(}flO2-II_<{F zg?QR}HS3|8vu)||T>1myhr?tbAx1+`@3Ix}<4YPiz#7hFi98af5wDVyi*u9hTw- z3w*20N+a&69RzNL_sb--zFCKkCR(P3Zx0gqzzHTZSS~BAQaT7;!Km{j+@eM+u`^6Q zw8500CT1-0<;eqKg(7mC9ewck^TzI*efu*ufSZs0@L+MhlL<8-=;#?%STZub)QXJ9FxWJzObT* zJv4eCL;_QY>RX=-SwbMGoN-Jj?vHbu5M39Pq*QyaqE^G9#yD4+L2jEQw<%l4CDwUc zdQJ7c+q^bheAoVrMIt<(T%1uV@ApB(jXKUFOt!wT>*Xbhr7Mx>RW*%O zpY?lhvqjYSRJJPgY9Q~GS2QIMMjebQC}klV7G837iiX7@rMAozds(7>zT~nkac)Y; z=JfcgpyV_1FDtEE zu1%VhRStrv;-M0ytr1=HN?jhqJ>-)AHY#@g>~4pgne4f2e%DvckB0?5(+`R*4c1{Q zbt~&z9cPu{E?;i8)$+JDC!oY%Ejp`s;U)msL@=~oI1Z0xs6^`F>@dijkvQ+ zx1)}OdGqIQofGC$!ZqrVgI9q+_Hp$K9}A$mH?joZ?7#2IIEqKNG+em1nnc)PZmQ2s z*2jiv69l&m;7;_^B?&q+M{p;qY3m0-T1(FKu-%02Arw9XgjVYvHX8^cU|4yZnu0b6 z{6vcnxQTA56Y0Pk68V`ROla2ABq6W@H_<*d++=$zn4xS_Q=b2ZmO41}!C?+!XaZUlBAkZMtdKH-Ak?+nggOrx&mnB} zvq~XA*{Y!o$pTReEhQ8Pn0?Cfd*alwT39H+!)-zlQGH}%n)N)XGmk6#W+%xXZ^JL! zXmI6~)RZM{;=$0?Jr_6`%({iGM~S7~{o7iZolI?TL(3=Va0nD^nGw0(PY zN&8#&50=slzGb`B5+}lXi4%R5(G&hTRItv#Vs$2O@NSm<+|g2K=9SH`pX2-OLxn4v zwn^x=^lAKRkL>@r)I7pnwmToo%a!UpXujHgBk-5qH@FZRZAP65Y)0R6U_RxCFnX8* z@h|a;e|=Z{v$>*c)fHW*Ah=r{Ab|fi9e`-ZZ$_5r_tl}pFCWIw!vsKdPgHT>WIZE= z_rdD*`i>q>AJ|n)(%W7z34WX8xu!*CDj=>i3+^ zANBjrCh3KHzqYqn{y`3YT09}fu3Ny2{c4hPZr>pS8$38{edf-TVnj|;Ei3ZelR2bc z@HpVJHvuZtGzF!AZSZiK?~}x@e8N%6D%wZw?oZS8xx@Q-F>PYtPXln1^mIvL0FJmx zp4x&jfSI^S0LN&6qc+845DSi$M=f00rGCmX%Ej?bNww!@VdK;kQw2(1!y&g=!vq;R!AUE-EmWQ=CP5%3;x&3U#4!rv_uY{1AH zSN7Z(JAbO}cgRe4A18bM1G0-0P6n8;Ej_eL@_*1YsNPYZvw(Y!ROEsV5t5GqH~GLX zmjwp)Q_+A9kuE?*2n!r009WJ{Y`s^otz5x&6$+nTg~AV4gw?-_oa|ST^Ka7~Edr2- zHQ4I{?6wX@$w<0CFI$93U-8l&Q7wiX$Qgf>x%)E8H)zC z>>*-ii^26SXO(W+ohYM8g^%Wvjio%HSj{o|N`{xYY~)WRh)j{Z&9agOJ=if{$OwE2 z!@34IFr;*Qc&ABXlJ96ma-C6_o=E_bcI{f(?$<& z6X5cWSeHZ(ZjEqxC!j5;2e$LM#o@@x3q{LAhIZRk6Bd* zy&9XXSEub&x8ai6Tkznyb<6Sm@u!!JIWD!AVigxbfnGznQ*Q?QxqqLigML88ht*Z~!UGW#CkY~E zh%%(A;12^GrM*Hp<_h6|ny$kB*vjwX6xoHkY6*39g)?6h1nD@0VCESDfXE zCU^vjGD>V<<+DHh9#ly!o^Ag*HRs&6nzZ`KSBcZkBhyO;%=^a*Rcy=LxTCl0%Ff>P zi{+CMv{h@K=rzem_HCcEzuq}5?YwcD*674Q9a%}Hbs<`M*f0|AaVDfHP7XPCK9jaC)>Bmq`mk)Yz2 z?*qM@LCDgpG1|!Gs%qiov%tXq)b-aNK2K*pJWX+DhxRP~=UQ>%qT7tlHOmt0Gq(a{ zi@DnOa@A@tvVJ;V`QaIa7S!Kq{L*Ds{g=H>|E8_;AKhTjcE34E#^SO3kvATpfoGDlOa0c3VTZQ$J8s8pH)^5w%%7)gRJtxM^E(Suaq7Jz#)i zkKgscEYqB;LCIAr0VSLLcv9ktWJ#z`K@$liXK(1iTYy82Qcld){5ydsT6T;21 z-J(HU7y@LU2KqlT)I;nh?fYJC5FvTH0jt{7< zy3FZiuKpL(Y}vVP+W&ef>@WS)YDcw8*0-1xN~~9n^Wdw-xd827WO0FOw!F-m2K64| z&E?2*%0cgNR1@$5V(leB#5z@ii1pVAP)X>2MXZ)9=`@3Q_z%;=sg(TBrkf7QE_wKW zEBnW+qc%XsZFYne2^fa-i9p$Fk@zM?pbuNbbfZ`>7DE}uS|4EEfAQ;>;{ie2=Gpn< z@92NJygn;OIfn)&c5j9`=%Gk0T)5A8%Qg0FO=-7pp}!gZE?t+j=FR%4h0&>DNk9m* zTMW~N2Hq9OM?^;-UGh-spbdkLez3q<>Yx=2${|k&80?mOqot=hJ&1VgQ~CL<3%4xG zH?G+WycqHp90m41%V_>h+ld75$Y=uZ$mrC=pcyjecC#fq8L$FM#d1sFOp4|s3)c;S z--@43d`=ZJi2YKc)Xr1Ok2?SOm!$Q^(sjGY_#>XV&?MRQmlf_5JtMX4duyWDyS-H>Agus!tg7 z8mzJF1r;9{dg7`dJD|^v&oi&EY34KQQDho>yl@?_=+N-9sSs%vUrTddhiiq#Kl!29G6-RZBcdAj3 z8W_dP{LT4qhR3|p=M%76L}gc?jS+J=yWL-h-z~p9x2|7b6>+eh&T*@K$5;1oOL_dm zJFc@!zv9i>IFz|f@n#gN(P!f`S+z6E?23yWRzs(_)59eXmCofi+(tpGYA?2@tot3p zFTPCgG4^YwPCJWaOru*C-&}^rx+!gDvr+6~N{}~l;N>_hLmRb-g~)Eq zbIRDEQZ(CZw{T2aGbwsx5}p>)W{1@m$<|YbB`JRBV2e>|RTAkY4G7p#Bu%C}Uyo9y8|6 zf|U|WWO0DbbLKDFgJ$z4SOJ>8D_aCB_S(0Z_0rOeJse!eL%Vg}FzBVl8GA^(j<0v= z*a8?Mj3Y<2eN)rIQau>Kj4mAs!j+=Yx5a)KJvvrT9}_Ke9i>SF3$R+|s(vI=N$sD- z{=YU3(dZ`$D}KqldkL`L{ZnQmqF+Do>U=dD8Nak!(G~wyYC6;BJ&)zxpPl!_eu#n< z&biV>%9jx9q$%<1)+}xue_TZa=Uz%7a{S4M9N7Ku*nB_goN~6;6>h@ImH&iGaO@84 z3%GN8qAZwSh9qupE^a4?o7*$%&8d_0c$^bdf3@|yJec7YQGUu?H=*&MC(V-}*maiz zktOOS_mnw$uA)Od9o0Kb+EXP!5PYym(XQ2K+LVJA-#)`5qFm$anl@F;gM~Kf&X)q1hWdY~H+ac;ykUZ~>f__-XpmPxA zuvccbTL37l^&!2N@3|^+WIg@NZ6u)y6<)!s^Tg~I zslP#~eW;dOUGFoK1Yu`1uV*Z5mueg96?GA+)$jz#gCD)+nR2;w~S zkpgp9Q^$;2 z?hy%l5O2Q=MjgVe67|PXt!g`sIeT&5kn4-c#A$bqzlfm({2;hhExiexAcjtii?E6r z^l^+5XQfI!*iz%t^NO6)6#HoZ!>C9^HBfOVZXWN5z6(w4F1dHYuB2r-<-=ba0cPcX zj-A;JOV;n2o?Rj~@0)rIimy>Ll-#{s;G%W}NCjo^2OS4hZ+w1zH&$Fwy zx__0OYrPfzEvYE3(dX>c#P3<E1uB~>kXDz_7Jf2fe^npiP)cX!gC{@zHTQ7T_?&o<*; z+wJ#B5gQhc>zm#0JqcdAu23M-jg92qGYQOT{Om4;Tx-BJTwX z9S6=FY=JY4UUFGX51zK%A<)0?P(Ns$7SMfEqBSn z%IAVUH#e2++%Q&Yb{5;){EoSn*4SezxI3ov>&7Q?uGG3MoB_i!VcYtRh?Qp#bws4E zjk^(_hu_E*Cc`C62Q1So-qwBm|I3SE$`e`VCp09cb ziei6nrUWa-M^Zq93zUl0w*FNPBmO{N&w>FW(UN>S%`q8WDo&8 zEFl6qLInKVGG=7&1S^6~#MTq}t0j&l*5i=I zkJKnq=7za?KUeRn2=}ZVQu{ut2LVfk69lX{w#|aE%_1%MFg2j>(nRK?V1pk zs3Q9Gu4Bwj6lwiVG1~oVn5VOLe zeT*-6?z6%Xe>^HP07&PS6%&KrU#|{;OT`2@f#Jm9y)!#%%mX6Q2L^BJz?r|A+Ri}l zUf1$oPp{tUN2^;Luz{x#!RqYXe%TCI*OcYId5>ydso`DLHT>*hj@4lD9{1fi@o7mZ zJDG;T^V7-;ZVAo#Z>Eypg;#~5@$HAu|Kv9ct<3C&!+)Ag({i_5UvY5UHR`JOBp`BK zpg?FmkKaBoV^VZvmnH|U=1}6L{s#PkzG3Ucu+sy*@ z<#xz*9Hl5l>n@2w2ByLWNcG>W{HZnURMlTR0G$5u9UJ3eY^J)Zw? za-MH*`lH_7>_=fSJ>o|Oeu7LZ%$^XPpL${pLnrNns4I?6@8kI~ErOS`p9=AOV!&0pns=D1mMzc~U*p<-%GhCreb#eoq#etxjk#RQV8!O+*ln!XOI% zlj&`RxMgq^+%O?-SwDri#a=Sdkj?H{;qxY2ImL7!EOvqpOGL{v6YJJIborBykXy<1aKEB*Urik!?*iPp}=H~E>Gmk)seIQ*brka|o_c#=jqe7l2eNf3KUU9+Y1E#;%IXwMD~r^KixE@U2u? zJLEjc@!%4;ui#hVgw?RzOIAyzA5?Za5=r$&cary0^LNvxZn37{benUdme)p{J1>Ll zrfoy|vA8X>VM(G+Zo+ka4VqnD03RM#M~Zh5#$IyZKW@xvbaerIcwDzB?5&nN8ZaN! z9|v39Umj?|K}5`WgDKe1lpGIj9!TIKfWXnvaX~I1aGa39nG_n+C!!JG#qpjz|MGNF zXDi6Q{P^ZqQ}NH6<3XRwic8?Wwq?Hls>Tbf{C>?Fo)e^6UrKn9RRelu!fKI)^P9Ki zk9GVXk8u-{3(c=5V%XeFjw&?gH!m#T42o;m2;s|<0=h;fM8;Hb?vWOr>cQ$do@A$t z{-X+HcB)S-Sp|WpoeD8UN3h?65{7$z7Tx9H&=`j)?)*@OC|EFb%)shtNSs`n43VtW zQ)wbu39Vc@AzDnj%7_1DmhSpm%3Ymmx!)^y(|{xff%`8^MQ312k85~$-C+8bs)*)5 zUM%Ozn|k4-Fe2xbSqeG(A^fIVGNGi7j0~3AW9}a?e{;GmyWaEa*PBD6?#GQOPPuAM znkScQDh8`jW;;fIrgvzB>Xao2jS$YMFXd>+Z6`GDb^>Kd<|7m&6Euj8m50U&K9^y6X0 zugX2esJ4zbe*Y@0p4^l)<)WHD-jK8JkTBJfFwpXxXQc{=^dgQjw5N;rXmg#|fA;*f z>GByT)&cwPBLVNjM@M3AC1QhlP^?)mFVRT=0)S}?z4 zbZGjHbqwN*nh@Lys*SAZj>_JmPXXVRfrKp*5;kxfV9+Wnaf{C3*)_O5=R&l$(45E! z_AXgKmn#ziO_Y@dcL5~%2eVx<&tExXen7#`vn?m|U4E|5&$DQNzw%<=ZH{V$wE99J zJ^5Q*FqaXP#9F)Y#E1OZjYA5iOUhbtUuCBQJIBNX3*Wh6BA*K^{OGlv<`=0T!=lKa z(187&d_k@R@bE>-NHs43@iHUPMCGu3*-qRps*c^VuD0# z3equ9Hj&yCLQ^@nuM-n*sQmt% zP!+95F4bG}$F%aQmgF2nXzenKF7=6E3<{n^weoT{T1MI$8+tB00Hvx|2MF=$E+E9) z&b*Xcc;NYqMmOCBRH?S0N?mvmJ*VN-0kY=weULTVf~z`;2F-RoybMAjEGLyC8-_Zu^<4Z(-LS~(mLg%M(e zodv|1e;k5#4}(>G{gMd1)ikCn@@?ZTYDYVbL3*&D@-|w&P}FckU5#-OckKAW3l9l- zLp6{2a=mRo9?!P0ViUNUAK&E62yv9%qW2H`2A+Q_^!%M?4CNTeJ?Av;bT$Fk{ycDrLUN@BxvC(S1w}vlAGQSI z@cOxvnDKr?HmZg5X5UU$ss+?SGHN)vp$b|4A+2gc);#Vz;_zM7fNt3>rDGk}SK4|v zvl~dSjgP;|FS{k^@hB5$4@L2-_qKHLSn@CVrKVox*wVeF2#lE4x%GQ)h3&T>;kbb< zP?48<15bby$q?PLdb)S0;%8zdsYMG3&@3MmYkRAq#|WPBU&QFYWeJR|tmyy=+(#j$ z`uo|6&_mCYhA#Y%!;O;fU9NQzAzP%nr+EcC=^DcfMN%IbuwKf8a7e(K16& z`EEyKsxG+vrL%MLzF^4YDW8hn&mZ8W;G;KgNq;MEbdaBGL>*>;+;Fu}|AUN#eH; zBDR^Rk(}H0KdmczoJtamdFSU94YSYQQyF8=GwQQm%r}0WSf!}(N!m@UIk^6Ph?BfL z)uY<*EHfr3C*4P#|8%|kwslYnH1xU}#$9=T0`JYpbf3q%2a@C0i(QB|x^G94uVn~t z@>L&50*#5qhH7<;EQeIxjg~tGZ+#%uUk(Bf^Iw{qm9f}Gw9r-OQ#5M&)X=3dfCM5F zEa1trUV$J5f$(pKVnoK`$Q178C*pZcWVUBr-#_oFjw^5+eVl8Yc~*VPgmbkN^JFd9 z?APJYlOR0-8#OMkhjk~9mWG5``vni8qmaBvIr`@eVX)4Mku= zz7Y|O{*TbKveKO7j*md#c|jpK6d?kg1pUo^C=BpIVIYEO(~h$`qaFo4@m?v$kLN^{ z4W-YlX^gRt&WT##OdMw0BG#orpOGJc)Tdp`a(Jt4_T!eU$0M8#p*u~EnWJtvJ-2wo zZf7}UjzB}&tKl#-BoVtEP(uE?=%((H!C~X;6wg~o?@U}&JEtz+ z4w4>Cmzev1COXspzxJ*>tf_6;2kAwrO7Dh4lPZK>M5T9W(xgN>p-Kk?#veG3z2%t?>nW8);5@ma&NfdF=r)+vTheHVp8q_P(Fxzhu3~uqr>;cNkUx!!~8x zFvKRfY61OTgS~!*PZC#aHh)VW7h5+)c5wiiN}n$#pdSN9ZC+0yVsUBlF`0`P5Tbui zSa_DQ0H9yW0*+&_0lfVnBocEo{3qmHf2}L8*B$>Cu4?qZ^?#0oSk9$?eC_Iw+;^?> z5gH#AkWEnW3g6o~%v7nAC52pa>$|xEaBRW=z%jKVkg2zqdKJb+P)ZWCN4DofsN7sv zf!Q=aML{b}mT3ai&tPf;%sK$v-7w&80vxWa)Bw15wgceq_WconCZhRn%9Q3hGky4;W3n`pdAytZFv>D_61nD)`b0#-%ucMpAER8J1@HRTtk?d3JmO z>4PlCr@+5r?8amH!46^A8yHp&U^}~|@+2O4wsHT_oye-W<~z_C0$BJ5ahCqzm?mfM zAb-P}320%k8~|?oH}bTwHxy_u{nYx8Il#j{yj}|aFMQNef9qe3J2Aj`*k^m=Wo=c8 z#o3D!*WKt!J>RLZ{!RVR4Vk#hnty22Ry-k)M8G1pwQt=RA( zT#yMp@v$#UP}v1zed;`f!F`(4(S`*qZ~&Nbfn4x``7=BPPm$9NE&wiEMsxvS;P(_Y z1cp%zk%v@3hJIgDo|E1X!(!dTuu6wiIAuCyAFmlk9cTtDMDi-huY1Pfa$z9i7b1Q6 zs?4=N57TZS!X%42WB`!=nRKeiV;ZmWK&byO$z~x}=j?fvE_$44IzOmyI)7lSXlKLB z#VKx9EvWZaK-}a>^qrf4SIJ%~0O&xW=zdbKQfNx-xqUTE3CPe{DFDZYw*ii2a|H@m zB@>JYaBO%R;8-?{W0e5MvQhw!4R1^Lyqz?1ViGvN|EIw9DNWuo9Z23f5}6Ly`%q9y z(kr1C1$`t+wrPm>7&|>0U}HBhYyyTQ%P$XB>=1HUZpyZzO}@%0kr@0%?gz^vktqL# z)i$qK8^8C*7YnCGxki>@#q>7tGii2Wg*25--ZFtUt{k9urjRD_$KV|>0KS80v&&|p zHKJK0(YA>TW9=vP1X97pZLJ@JtI#Jvar&bS{XwMoBRgBddH)k4edDW8Vrp=GK(>|U z4)8GPg%2C0dZ?x`3!{sTb5i4jl`~m}Hgi{`$vgzHSOSs0Zy1U?eld40*Yo1Z7rCGz zOP#U>4`D&vonUW`WjWF|} zVBgJjq`cDFn0s-q1Z=PG#ajd>I@ z6`q+&=*Iw)n_UGPhSGi_j0M!ixmHJ;z@#;Q!Q6g*SU(%fsZ?ot^f;S9a?gg#tt-!J z(xNBWw3550`&5dMMSUwCZ8w5jO*CFOlFRk3c=X-~ZZpxq2Oi|zyW&53C88#+(K2G* zl5g$ZjYk+sNMub~%)>0f1X)1h_+w2pMlvXvbf8Tvwdy8g-`ru+uXqcT(u2&yWpx*4 zu9UOJs7L--9G`yyX0FTvBro3KfZfICs@LKET@jJ#@%%pEr@H%KG*3D}87mhCIlCZt zuW588)e|S_#zS@zX4DYZM&=q`S9WuK=~C?bm2`P=ce`$G zfazri8GDVc3IEZwc&T$1?6T@aU?!efhX=pRtL;hZ?!<0a>XGbJKKb6Le4U3-i5z6Z%W9b1wu_W?;j~24rz{t_pQ@g19_z zBW_u2HnxhqbjpHp94E~zQmoQ}-}UL2e-KS0g6AdA+DDbh&4soeR-TwL*<{@VtY z+4)S;<;L{Z`rc^t%l5!CP(*D8X`aHa&E`^b|Sofa|t8*RJVn`6-vdk(LW0> z$RPig`{-!>DFipdsA9v9OCHu($77yt?}aR=uD3*91VeBOIw6C4b%^izk=RT=2z z(eG4N=PA__8Wc9r;p|o$cv`BuPu!H9t3srV1cQ+^0%rH^EmiEv!)*$4ViPmXuPm)k ze_2mjqlQx3gx#nPPAOF>3HwDpN06~2gvcR??QsW!=>Erzgc+ossSYzZ|TJ^Jh~^1T5ag)fW)|; zye>$7OU!n2MR|gF_mqjPD{{cDY};3pV@YLNL1)1_npRV3!kBU+z-@0SW_J<$dR#I~ ztmZP-u5X;qhI}QrjX>lIV@5nqps@?N6>hbqQ97|dp|6$x)c}N1l%Tp4e4b`h<{|HT zWY$aljhSgqX2IXp;iK5X9Q_scmdZ5S7gB=+HHp|9gSd}PNmo-2dI)QDm@EysPv34K zR|g7a>x|p+Yshr{tX18t4w8CSt{fZ;_HreT?Bt+09VbGI0QPnEGe*4^FW*8=yf;!t3j+T69(b__<^&GZ6 z?-w|^H_BU&1wCE8)+n=C*%B89$qFA@_NSh4teoy+Xb9sKlk{kj?8}{_Tm5zgcc>(a zPr5ziH+%EK_ETDFE08Tq>njeWBHgkk;qXPXkH6nl;MRRU!KC8a0wy`PAd_1kyL35+ z@kWyp@F_c(HQn7Mv6E5i$5Gdt@B=+;pjcjk%+-6Njw063KHuA^`7_YOj6={IA-%+} ztQv{X8fFx#1ATeqwT?ierb*3FT~SHUi-I5&YOH$h67FkgAI4>dLKYa92OaJ62c4pr z%_S;Ma554XDCsC)YIK5qd#$|XtcX%&Q(mPitFfuwB4h}5ez6(%ig&(?PMG?WrX!`y z^;oD$(|Li-jfrxlLMqSR3xQX+<^|UZ2f{``q(>%TVom+7Vd)yf1lJhBMwdS6rk5-8 zL)>A9DyH1{aeq6E7l^*^&h}n)HSX!!E%8g%=WVMV=Yqx^qB@+uyTd-u#QE7@T=gEAz;fAi7cT0Zn3+=tee#pcO2WX<71;{ z_%k8p36VOk{czwB&IrXeIXr6f0xAys`i!VV#9k2sRE z*$rxO$$Kut!eCv{H!OL3$iyZ zZt{bKR3Cks$g_#eV#gObGdbsSKl)rZCB8Q{avs^$O%5t*H`VmIJ<$;JA(M}|67sF$ zUW*u*m?!nw_XMtQ);O?MACJWIUD@j{kR#6zF8TK~Q!UHsi0F!rUwo_7V4HBmR_#s+ z0k2bxdlN+TD+s4OX}~>lZc!7mIhyN(vk2Vd8@S{f-7ky`i~`CINQ zf;QW)M~4Tn)?hHlOY3Ec5|5z#%CtAR7sOQTj(|DGWzANa|)>7AbsUW421X zFE*#s{Y5G?^(s`gWdB^ktPbc)Hl@nllp^3J6}S4%X{vXtJW2KTWG0 znep13Z1VSX7|UW`@I*RoOu<+GB`uLz$ebL`0t&{`6eq{SfO}LzeT_TRVz6+u)uMdZ zdCWIKev;w2^khZq((D>jt-ySiEJJihRLL*{=+o{AU4j`xH1GEGyFjsP_mAF^&ikL& zgjCa{ySp1VyvTM(SiQ zGMrxRA&Q>LtIGEn6y<_NX;^3_*7^hIA+J46ow5}U4JbsoZ<);f5*0xH z&P!dn2%p!!#mMoTI>X5#@q1c{o+COSQ-X5(BiK?rJ8fZdc5x~&>Eb)b$CbpzI+4%A zR|xbwjvsd0uhx22eFlHz_YR}orZRymwLB>NT1!E*Qd#>z2lB1H)R?WsF;sx3oRtMk zktv$+`g_@(cwR!VTS2K~LGR-|gGl0voF@CF7_G-5C_@~5b_q7$Bl7fk>yU~1Sn8)+ z``IkCfxWw<>9w5G>{$CgN`dRX&B5DIVw85vL>F>!va=RWw_YoU2AvnutjGjZ%)TpPbMRWc(k_v4q}2YB_2@l4WHvdNCmf z?!$R;ed43fZ|1BXi=*)6?3d~8vcq>t$e*%~-k}H{P~kNsqAXyXgYgVfg?sl` zZ=kTp5R2!9LKm-);YcXm~EV}HO(7H!XtLQhs9jG%o4oYO|@(cSRp5VFW0wSJc$Rw~Sy=Uk?){du(h{y>lAW5W9J`dSn}| z>Ed9@^Mf$T=uc}A_au(4?423!ffuM}$q!rcN4 zx+AkU@@U&MtZ^+TDPu44v+bhV=cKoOr=#ITf2mE2*Oy(f`)US{(9Lg`b zEYgc4h>r}Kl>`Fr6I7Q!s|(UoX#~IkjsCSB$ZfkeRt&(y67WOy516pEaD!Uwdbrs+ z+x$QalW2MSRsphmCtf8WxbKxlgoGastc{PDDoy0ZI;v3Iotq`7B0@lj&-qD+nYXWXFhj{3 z&TEbISzYTI8YBEWKO%*ADIlpDPw@Ecr*8L-WE07_W;VJm4IPgQs7Cbed(dV(-?61g zs@~ZYh)(4_fleKMOdy@&an5CN7o%u34*=Hkw81#^R9t`bW&4i;w?2vgJkpMFKg_h_^VP~AOI zx=>gSSj-{i)Q?xu@*Go9q6(g*J+F$QleMnPx%5j@RM0Xisj%7o-kUIjRE9TG(-?E6 zyuJBK8x+rF)@l0O4ASHB{K{6|a>WA*ArHKWRpgF(_ic;rrgCk%NS-FVjGMPCBozGC zZZ}VsNfX$!@5P#ZuM!!6nielzBlgZB8y_U}7t7aWq+~%hJ>PiD)<wcW_?yph2@B-D`rw|=<&^hV8&`v<~&?0&nc zp26GW^8o@dM&CpOs1MKgrgeux{-rK0wdjHj^hCwA$Mf%!W&|HUDr#(7FU0CE9@QMr zk$bzaNEms4BG(SZLD5_|g6oZL=h# zxs3=|_R~W&-an%l>&h+Q)c$90FN4<+<^aSo8SWpwy`NVO!x<~UoIRk<9_9~xT%hiz zn84Oj{i}gWJpuMZV5u4a!^D5l&H-)>b2q4?J3k+W{;IgKja~Q}Sh52&88OH3&sP_) zSD?q}1#@$-guxvC?p@>Y;oCUCwq*eWbU!r#?j_L=aVHUuNHpIc79sWD*a*MFS*aJ+J8NUf9irjM`a+;za7fIs{a*7|E%s_ b`6u;%psJQCE)W}-KqCe<0s$xfJ7)A>Zebo; literal 0 HcmV?d00001 diff --git a/src/LRUCache.js b/src/LRUCache.js new file mode 100644 index 0000000..0c65f62 --- /dev/null +++ b/src/LRUCache.js @@ -0,0 +1,34 @@ +class LRUCache { + constructor(capacity = 500) { + this.cache = new Map(); + this.capacity = capacity; + } + + clear() { + this.cache = new Map(); + } + + get(key) { + if (!this.cache.has(key)) return null; + + let val = this.cache.get(key); + + this.cache.delete(key); + this.cache.set(key, val); + + return val; + } + + set(key, value) { + this.cache.delete(key); + + if (this.cache.size === this.capacity) { + this.cache.delete(this.cache.keys().next().value); + this.cache.set(key, value); + } else { + this.cache.set(key, value); + } + } +} + +module.exports = LRUCache; diff --git a/src/Range.js b/src/Range.js index c3c3e8f..c6acba6 100644 --- a/src/Range.js +++ b/src/Range.js @@ -1,10 +1,13 @@ "use strict"; +const LRUCache = require('./LRUCache.js'); const col_str_2_int = require('./col_str_2_int.js'); const int_2_col_str = require('./int_2_col_str.js'); const getSanitizedSheetName = require('./getSanitizedSheetName.js'); -module.exports = function Range(str_expression, formula) { +const Cache = new LRUCache() + +function Range(str_expression, formula) { this.parse = function() { var range_expression, sheet_name, sheet; if (str_expression.indexOf('!') != -1) { @@ -37,7 +40,8 @@ module.exports = function Range(str_expression, formula) { max_col: max_col, }; }; - this.calc = function() { + + this._calc = function() { var results = this.parse(); var sheet_name = results.sheet_name; var sheet = results.sheet; @@ -84,4 +88,20 @@ module.exports = function Range(str_expression, formula) { } return matrix; }; + + this.calc = function() { + const cached = Cache.get(str_expression); + if(cached) { + return cached; + } + else { + const result = this._calc(); + Cache.set(str_expression, result); + return result; + } + } }; + +Range.cache = Cache + +module.exports = Range; diff --git a/src/index.js b/src/index.js index b7dd452..3fadab2 100644 --- a/src/index.js +++ b/src/index.js @@ -5,8 +5,11 @@ const col_str_2_int = require('./col_str_2_int.js'); const exec_formula = require('./exec_formula.js'); const find_all_cells_with_formulas = require('./find_all_cells_with_formulas.js'); const Calculator = require('./Calculator.js'); +const { cache: RangeCache } = require('./Range.js'); var mymodule = function(workbook, options) { + RangeCache.clear(); + var formulas = find_all_cells_with_formulas(workbook, exec_formula); for (var i = formulas.length - 1; i >= 0; i--) { try { diff --git a/test/1-basic-test.js b/test/1-basic-test.js index 45e301c..8a503d1 100644 --- a/test/1-basic-test.js +++ b/test/1-basic-test.js @@ -783,7 +783,10 @@ describe('XLSX_CALC', function() { workbook.Sheets.Sheet1.A3 = { t: 'n', v: 2 }; workbook.Sheets.Sheet1.B1 = { f: 'A1:A3' }; var exec_formula = require('../src/exec_formula.js'), - find_all_cells_with_formulas = require('../src/find_all_cells_with_formulas.js'); + find_all_cells_with_formulas = require('../src/find_all_cells_with_formulas.js'), + cache = require('../src/Range.js').cache; + cache.clear(); + var formula = find_all_cells_with_formulas(workbook, exec_formula)[0]; var range = exec_formula.build_expression(formula).args[0].calc(); var expected = [ @@ -2252,7 +2255,7 @@ describe('XLSX_CALC', function() { assert.equal(workbook.Sheets.Sheet1.B8.v, 6) }) }) - + describe('INDEX', function () { it('returns the value of an element in a matrix, selected by the row and column number indexes', function () { workbook.Sheets.Sheet1.A1 = { v: 'Data' }; diff --git a/test/9-lru-cache.js b/test/9-lru-cache.js new file mode 100644 index 0000000..bbef808 --- /dev/null +++ b/test/9-lru-cache.js @@ -0,0 +1,56 @@ +"use strict"; +const LRUCache = require('../src/LRUCache.js'); +const assert = require('assert'); + +describe('LRU cache', () => { + it('should return null if missing from cache', () => { + const cache = new LRUCache() + assert.equal(cache.get('key'), null); + }); + + it('should cache results', () => { + const cache = new LRUCache() + cache.set('key', 'value') + assert.equal(cache.get('key'), 'value'); + }); + + it('should remove least recently used if at capacity', () => { + const cache = new LRUCache(2) + cache.set('key1', 'value1') + cache.set('key2', 'value2') + cache.set('key3', 'value3') + + // assert keys + assert.equal(cache.get('key1'), null); + assert.equal(cache.get('key2'), 'value2'); + assert.equal(cache.get('key3'), 'value3'); + }); + + + it('should update cache when accessed', () => { + const cache = new LRUCache(2) + cache.set('key1', 'value1') + cache.set('key2', 'value2') + // accessing key 1 to update recently used + cache.get('key1') + + cache.set('key3', 'value3') + + assert.equal(cache.get('key1'), 'value1'); + assert.equal(cache.get('key2'), null); + assert.equal(cache.get('key3'), 'value3'); + }); + + it('should empty cache when cleared', () => { + const cache = new LRUCache() + cache.set('key1', 'value1') + cache.set('key2', 'value2') + cache.set('key3', 'value3') + + cache.clear(); + + assert.equal(cache.get('key1'), null); + assert.equal(cache.get('key2'), null); + assert.equal(cache.get('key3'), null); + }); +}) From c47f5de0f43cf0c88b222dd5e150f98d3abce7ce Mon Sep 17 00:00:00 2001 From: Luke Dunscombe Date: Sun, 15 Sep 2024 11:25:17 -0500 Subject: [PATCH 2/3] adjuisting benchmarking message --- benchmarking/range.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmarking/range.js b/benchmarking/range.js index c3cce04..6f1198d 100644 --- a/benchmarking/range.js +++ b/benchmarking/range.js @@ -18,4 +18,4 @@ for (let i = 0; i < n; i++) { } const average = (times.reduce((sum, t) => sum + t, 0)) / n; -console.log(`Average time for ${n} executions: ${average}`) +console.log(`Average time for ${n} executions: ${average.toFixed(2)} ms`) From c1a7261e862b7c5c8da7a2bf5fff081f7a8a24a7 Mon Sep 17 00:00:00 2001 From: Luke Dunscombe Date: Sun, 15 Sep 2024 11:32:51 -0500 Subject: [PATCH 3/3] moving cache clear to end of function --- src/index.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/index.js b/src/index.js index 3fadab2..5bce807 100644 --- a/src/index.js +++ b/src/index.js @@ -8,8 +8,6 @@ const Calculator = require('./Calculator.js'); const { cache: RangeCache } = require('./Range.js'); var mymodule = function(workbook, options) { - RangeCache.clear(); - var formulas = find_all_cells_with_formulas(workbook, exec_formula); for (var i = formulas.length - 1; i >= 0; i--) { try { @@ -27,6 +25,9 @@ var mymodule = function(workbook, options) { } } } + + // Clear out cache for next calculation + RangeCache.clear(); }; mymodule.calculator = function calculator(workbook) {