From 3142dab09155cff71444492cef87b7cbf73f13db Mon Sep 17 00:00:00 2001 From: hvgh88 Date: Fri, 31 Jan 2025 12:09:32 -0500 Subject: [PATCH 1/3] MapReduce design pattern added --- map-reduce/README.md | 233 ++++++++++++++++++ map-reduce/etc/map-reduce.png | Bin 0 -> 29252 bytes map-reduce/etc/map-reduce.urm.puml | 24 ++ map-reduce/pom.xml | 62 +++++ .../src/main/java/com/iluwatar/Main.java | 55 +++++ .../src/main/java/com/iluwatar/MapReduce.java | 55 +++++ .../src/main/java/com/iluwatar/Mapper.java | 56 +++++ .../src/main/java/com/iluwatar/Reducer.java | 56 +++++ .../src/main/java/com/iluwatar/Shuffler.java | 56 +++++ .../test/java/com/iluwatar/MapReduceTest.java | 48 ++++ .../test/java/com/iluwatar/MapperTest.java | 50 ++++ .../test/java/com/iluwatar/ReducerTest.java | 74 ++++++ .../test/java/com/iluwatar/ShufflerTest.java | 47 ++++ 13 files changed, 816 insertions(+) create mode 100644 map-reduce/README.md create mode 100644 map-reduce/etc/map-reduce.png create mode 100644 map-reduce/etc/map-reduce.urm.puml create mode 100644 map-reduce/pom.xml create mode 100644 map-reduce/src/main/java/com/iluwatar/Main.java create mode 100644 map-reduce/src/main/java/com/iluwatar/MapReduce.java create mode 100644 map-reduce/src/main/java/com/iluwatar/Mapper.java create mode 100644 map-reduce/src/main/java/com/iluwatar/Reducer.java create mode 100644 map-reduce/src/main/java/com/iluwatar/Shuffler.java create mode 100644 map-reduce/src/test/java/com/iluwatar/MapReduceTest.java create mode 100644 map-reduce/src/test/java/com/iluwatar/MapperTest.java create mode 100644 map-reduce/src/test/java/com/iluwatar/ReducerTest.java create mode 100644 map-reduce/src/test/java/com/iluwatar/ShufflerTest.java diff --git a/map-reduce/README.md b/map-reduce/README.md new file mode 100644 index 000000000000..a2eb3e9f3193 --- /dev/null +++ b/map-reduce/README.md @@ -0,0 +1,233 @@ +--- +title: "MapReduce Pattern in Java" +shortTitle: MapReduce +description: "Learn the MapReduce pattern in Java with real-world examples, class diagrams, and tutorials. Understand its intent, applicability, benefits, and known uses to enhance your design pattern knowledge." +category: Performance optimization +language: en +tag: + - Data processing + - Code simplification + - Delegation + - Performance +--- + +## Also known as + +* Split-Apply-Combine Strategy +* Scatter-Gather Pattern + +## Intent of Map Reduce Design Pattern + +MapReduce aims to process and generate large datasets with a parallel, distributed algorithm on a cluster. It divides the workload into two main phases: Map and Reduce, allowing for efficient parallel processing of data. + +## Detailed Explanation of Map Reduce Pattern with Real-World Examples + +Real-world example + +> Imagine a large e-commerce company that wants to analyze its sales data across multiple regions. They have terabytes of transaction data stored across hundreds of servers. Using MapReduce, they can efficiently process this data to calculate total sales by product category. The Map function would process individual sales records, emitting key-value pairs of (category, sale amount). The Reduce function would then sum up all sale amounts for each category, producing the final result. +> +> In this scenario, the Abstract Factory is an interface for creating families of related furniture objects (chairs, tables, sofas). Each concrete factory (ModernFurnitureFactory, VictorianFurnitureFactory, RusticFurnitureFactory) implements the Abstract Factory interface and creates a set of products that match the specific style. This way, clients can create a whole set of modern or Victorian furniture without worrying about the details of their instantiation. This maintains a consistent style and allows easy swapping of one style of furniture for another. + +In plain words + +> MapReduce splits a large problem into smaller parts, processes them in parallel, and then combines the results. + +Wikipedia says + +> "MapReduce is a programming model and associated implementation for processing and generating big data sets with a parallel, distributed algorithm on a cluster". +MapReduce consists of two main steps: +The "Map" step: The master node takes the input, divides it into smaller sub-problems, and distributes them to worker nodes. A worker node may do this again in turn, leading to a multi-level tree structure. The worker node processes the smaller problem, and passes the answer back to its master node. +The "Reduce" step: The master node then collects the answers to all the sub-problems and combines them in some way to form the output – the answer to the problem it was originally trying to solve. +This approach allows for efficient processing of vast amounts of data across multiple machines, making it a fundamental technique in big data analytics and distributed computing. + +## Programmatic Example of Map Reduce in Java + +### 1. Map Phase (Splitting & Processing Data) + +* The Mapper takes an input string, splits it into words, and counts occurrences. +* Output: A map {word → count} for each input line. +#### `Mapper.java` +```java +public class Mapper { + public static Map map(String input) { + Map wordCount = new HashMap<>(); + String[] words = input.split("\\s+"); + for (String word : words) { + word = word.toLowerCase().replaceAll("[^a-z]", ""); + if (!word.isEmpty()) { + wordCount.put(word, wordCount.getOrDefault(word, 0) + 1); + } + } + return wordCount; + } +} +``` +Example Input: ```"Hello world hello"``` +Output: ```{hello=2, world=1}``` + +### 2. Shuffle Phase (Grouping Data by Key) + +* The Shuffler collects key-value pairs from multiple mappers and groups values by key. +#### `Shuffler.java` +```java +public class Shuffler { + public static Map> shuffleAndSort(List> mapped) { + Map> grouped = new HashMap<>(); + for (Map map : mapped) { + for (Map.Entry entry : map.entrySet()) { + grouped.putIfAbsent(entry.getKey(), new ArrayList<>()); + grouped.get(entry.getKey()).add(entry.getValue()); + } + } + return grouped; + } +} +``` +Example Input: +``` +[ + {"hello": 2, "world": 1}, + {"hello": 1, "java": 1} +] +``` +Output: +``` +{ + "hello": [2, 1], + "world": [1], + "java": [1] +} +``` + +### 3. Reduce Phase (Aggregating Results) + +* The Reducer sums up occurrences of each word. +#### `Reducer.java` +```java +public class Reducer { + public static List> reduce(Map> grouped) { + Map reduced = new HashMap<>(); + for (Map.Entry> entry : grouped.entrySet()) { + reduced.put(entry.getKey(), entry.getValue().stream().mapToInt(Integer::intValue).sum()); + } + + List> result = new ArrayList<>(reduced.entrySet()); + result.sort(Map.Entry.comparingByValue(Comparator.reverseOrder())); + return result; + } +} +``` +Example Input: +``` +{ + "hello": [2, 1], + "world": [1], + "java": [1] +} +``` +Output: +``` +[ + {"hello": 3}, + {"world": 1}, + {"java": 1} +] +``` + +### 4. Running the Full MapReduce Process + +* The MapReduce class coordinates the three steps. +#### `MapReduce.java` +```java +public class MapReduce { + public static List> mapReduce(List inputs) { + List> mapped = new ArrayList<>(); + for (String input : inputs) { + mapped.add(Mapper.map(input)); + } + + Map> grouped = Shuffler.shuffleAndSort(mapped); + + return Reducer.reduce(grouped); + } +} +``` + +### 4. Main Execution (Calling MapReduce) + +* The Main class executes the MapReduce pipeline and prints the final word count. +#### `Main.java` +```java + public static void main(String[] args) { + List inputs = Arrays.asList( + "Hello world hello", + "MapReduce is fun", + "Hello from the other side", + "Hello world" + ); + List> result = MapReduce.mapReduce(inputs); + for (Map.Entry entry : result) { + System.out.println(entry.getKey() + ": " + entry.getValue()); + } +} +``` + +Output: +``` +hello: 4 +world: 2 +the: 1 +other: 1 +side: 1 +mapreduce: 1 +is: 1 +from: 1 +fun: 1 +``` + +## When to Use the Map Reduce Pattern in Java + +Use MapReduce when: +* Processing large datasets that don't fit into a single machine's memory +* Performing computations that can be parallelized +* Dealing with fault-tolerant and distributed computing scenarios +* Analyzing log files, web crawl data, or scientific data + +## Map Reduce Pattern Java Tutorials + +* [MapReduce Tutorial(Apache Hadoop)](https://hadoop.apache.org/docs/stable/hadoop-mapreduce-client/hadoop-mapreduce-client-core/MapReduceTutorial.html) +* [MapReduce Example(Simplilearn)](https://www.youtube.com/watch?v=l2clwKnrtO8) + +## Benefits and Trade-offs of Map Reduce Pattern + +Benefits: + +* Scalability: Can process vast amounts of data across multiple machines +* Fault-tolerance: Handles machine failures gracefully +* Simplicity: Abstracts complex distributed computing details + +Trade-offs: + +* Overhead: Not efficient for small datasets due to setup and coordination costs +* Limited flexibility: Not suitable for all types of computations or algorithms +* Latency: Batch-oriented nature may not be suitable for real-time processing needs + +## Real-World Applications of Map Reduce Pattern in Java + +* Google's original implementation for indexing web pages +* Hadoop MapReduce for big data processing +* Log analysis in large-scale systems +* Genomic sequence analysis in bioinformatics + +## Related Java Design Patterns + +* Chaining Pattern +* Master-Worker Pattern +* Pipeline Pattern + +## References and Credits + +* [What is MapReduce](https://www.ibm.com/think/topics/mapreduce) +* [Wy MapReduce is not dead](https://www.codemotion.com/magazine/ai-ml/big-data/mapreduce-not-dead-heres-why-its-still-ruling-in-the-cloud/) +* [Scalabe Distributed Data Processing Solutions](https://tcpp.cs.gsu.edu/curriculum/?q=system%2Ffiles%2Fch07.pdf) +* [Java Design Patterns: A Hands-On Experience with Real-World Examples](https://amzn.to/3HWNf4U) diff --git a/map-reduce/etc/map-reduce.png b/map-reduce/etc/map-reduce.png new file mode 100644 index 0000000000000000000000000000000000000000..caf764553291c10b1bebefeb6ecf7f85b745a3fa GIT binary patch literal 29252 zcmeFZby(F~*Dkz3(XEIm1`Q%0p|F$`q%4rmMYB*s5Gmlbs*F~_*aJ?=3-%E^ckouxR7Kp=?3pFC1P zAPBS&2t4h-@ZpoT-Q5@PKUS;9s#dyY<``rB=T-84XsKqfE*&L7F7T|1=>D-ARrYA9~UX zO)5k2PTpzeSSpNWJ6YKCCN$@S`1wK)jw#p7fas~w_SbogUDtbhVx-*|sC5nT({9hR zUMY5ZEN5i(YsNzgd!<9mz-#`wu86wRB7e}*0}6Vj!G);bEGa43Zm8Y|-=utQ_Qr7T zV0#IhPw!B(Z=-xz6x!m+BIV3Z1Z*7AwjOeW6fn_kO3Kt+ZU@mhKkyz z$vw+xc6+|jCP9&aSyXQFXSYCB0E_x)}syi>WCB^2Jo_nOa8pEAvyC4TVM ztD(;|xpwF7;>yJP%^VAU@{UYb+LwfW}^wxFc(Jvy) z`jMoCcFVutZ(kP_+{Yds6f7?t{mV7&;4uPUUtftJcCG6b9m$H5OC!}aenj5$|y}A(yi&QQAV99onK{XbqS6>USf(` z!H36t3GEnuy2@5nUH$$%wf)MNII7&FKi_$Ohufr&>-u$_xt^@a=BRGlkh`yb(|ZYI z>(%@E`}^mc^xMq!yqfJw$Gw}5PApejjzM#o!{!X-r=Fgk%F49&Lt ztLfOxK&19T$zplCm&MwYJd5M#7v43Jqni$!^^Otf&Cp!9aE)=kzo5c?J;lH7=i%1! zXb@7HT`}>qzwk;O{o&%_eud@aFI2iZN}1MviY_m%fzbb^%0j!WaDdj@_t$d{!{rU7xVP*JbQ&T z2^BzUxAgbvP9t;kZ)xXgg*4Ms!mHBMayn9cgM(Fzt)?4;kPd(5;jsP&a%rRAEOq-Z z=HYt*-u6Z>8o?BanlF@0sT?CJsjSGQ@0VngQ0uSXi2L10Mui+U&U_~4>`1ZLm{Vhs zl9AcCb@FKJ2Y%A%;W{6BqXS|6hRaT2hhU$Aj$7YYwQ~(yn?qdWqEV^J*?ontbn~Ry zSMX!4Z#<+sx^T(Q82)_Ooxw`b5+~*r92~r~WWHYZ*2l+(k)y(DDfw&UOh>X`-$WBH zR>@(U9qAn&erw#IIg;D|#L*30>V%NqK0eE1ezK=dok|ID>CM(JFzLTB-kqsU%Vp>t zI`YZe+k1O;au#>B`Syx_vV@cr*S}pIy|)^5D1ccR3(|IGA&eXkO>|oRL8|Sv-dS z&5CX51BY4~OMTDg&(boVJHFZ7PEUuutx+%79@IR>!C6OB?^;!9l@jTKdHwF)$q=Q( zX;~KzEw;-#Iy!IOym8(dIsM0BW=smA4*7&v@xM^=2Kq8Z+ErO~Vh^@zg&Qt{WtpVa zeFXQ{z?Z32(xrMd0@qoLY^Tew=F&>=mdfes0g;gz%By_H+s}WJ@eMd_4jRBUQO-Nb z@NZzZcaBYL*nUWHJ!+K`d4ZC0w~pTHmRk0sbcz*x4zr-XfSW2Qodkqe>t_5}6!o_E zH?cwEOi_*sC~?Bd&+pG_A1ahA4is@(?r8tUdHT-T?o0?&quJ^e&ijj;`{06dv5zAngLzE)CZk*y zwjVrr_%JRub`5nj?6U-m$ve$%*OAvwou^_>g-9+dEsb)huUM=&bk0PV>5YJ+^fl2F zLWT`GESA|0VGJNVccwX4yA!1lv@eo=>wO%7hP^IWph9x#l4KCO6L`WucEmpMXH0-8 zHjUOt-qW&=_>kJ|uXpR`+06Hi)_n2f+YaG0(Br0~i&03B$X@z&Z_yE)1}vHX__uH0 zmPc#7D7Yo0SJo@2ha*V8!9a!pf~?P5gQE^e85`64k0%LY@Ita0XI$}4=xukUYoJn; zSm1ebimzVPQrqSlbxKH3a2ZAmXK_C;0BG*tD6$ zGUWW;+%mzt{oT!|JG!D@D7ZV|#&+|0-Adx%MDJP8SS|m4ysI_0ieFz}4{>O&<^y|GZ{OFy zWLmMm0lOqgT@bnC<+7&>`vCAo%3dR~A{YBZ2p5*d+Y%(1Zru12JIrt5)_XgIw5|(h zV3`#M{7CGiErk0^Ofq}e!~LkNDs^@n`PkT4u#+zdM@CW@Fs(HB81uSn{0r}pH_XD6 z{6C&Tj~~T^^p=E|J9qBf=PzIWgq>lDL`CZr{G#r!vQg@JM*FZI{s6v#TB{Tp^tH{d z46U1)uNH^O{-@8u4ZV+!jt&kc`}2qeU49hK=i#>ZuTspa(5<#&>scGH5;JW1;GgNc z#I9AW7j7}x_5Qh{v&%REL@AZyU^Au$6Ti8LH4C?!44?MLYGYnQl#{%-I@x@f^h8op z(yzPjPuOzK1`rJQ9)dtfPEKA^WPARP!>vrVIhqLO34nrhigTqE`rt@q&_^F4R4k1= zO#04ajzgqoQ!-2bi+g{fnK}2@#Gb@??_#v^j;QlA&P2Z0zI8F~%l+tZ4)Ih1|2xdjXP;_eUq3%EJ&jRv1atV< ztHAetTjBi{f2QHGDY9ZOdztRx@@>lrsnNkT8v+xjG0q@S}jx7Fl`gDKy1>s{PpPZ>ay-z@_JPgwclm#kwy`>UI+}ZZEPgm{GUJG|foA{8EBf zXFEpV@zy%5yI{Jd#^NtqmsuO1c^)&Y@c_}c$1rlKXZ3&&Y{u&+#ooE`Miev1fj+X6MA7W*A%uV`xG*~=p^4-mj`o;VeY(*`6b@$`W=O>-KLtnJb2e=eMp)6Ob1Hu zJb&;R`S9*^L9H}J^M&)}^hm_rM7c4749$`@`eK9bp+RL@%}MGf=Y+GR3xozw6O&76 zW{IH@k9`HUPMjv5>)XYnEg_s%rI|Zyu~s-C@;4n)?wxql-jeH3Iq`^I@d8V4-8DhNGc#)KK8*CZ{14Wi!v-prqGT~cOab6Iz zXVvTu_Ao30_G?K7F$EA(ky^zas_IX?Nkv-ey&PAS*(_SMo-pM&H=fe9M6a}I^(eA^ zI&u1Z_&wWSs`FuEV`Gm$T_u;q>!wG_DVg+@7rn@EH*cy6t0-6%gbVh%nc}`(gPE-v z$GoO2Fn#nWfB8T}z481AYhXv&Vx;MQ3GZJe-4~R`4ElV9MXf(JY|NTKQp!5NHrLyp zv$R}G8&s8{RbqeDp7Qq7%GG9Jvs-Kou)ZIB-j^vVeI2kS%~t8JvvHnhpA&D?Z}3ls z=n2RpJ`eC$qyVO^P~PSgG0iO|-CG2jUZo;F41z`_o|}`!vI{*$x_BG)%(spf2!noF zz0sjOuR$66GrptaR zYmvpcgq)llAR!MMIq(|SMQJv`UM&W-o~Y!EB765X{SS)?UlajCG1~6Q(gpmo&>AP! zC7lhHVSd+9P|IM7A>L(q64o@2NlZ z`unQE-lt1wyAcB2I5lxOpW)VI#jk)GQhXC6g8{asQ6VzD@*6c8v?@X*RS^#m}Lf)T*csBzO(YJwsc7|1I*bdGAy{M z-vt)hFr@7J%6Oyxh4vKG&E)ktMuAm)ovL?d=;-KnQdBFPogk5|s;Y{Oi`$&<|9X~_ zgCm~Q2vWlmoB1&*_-3HWxr;ack5a5^QO=fr_QOud*{Armk40Zvz}E(jmyCP`))Pg#f=h%R>2fh|=B}4X zJb3T`%%iQnJwa0f(h5Bt9;MloR^6ea>l4TnmL4%F`Us$^O)L8Q>B3Uwb(DDifpNWE;7kRU%l|Lxo6@Sv0#AUYuNT6gqxC!*vr zAFc#Ye){z3<`7P?E9hb?+-~iiAkfQ%caja^c$}$_=J`A8%rFvSK)mR)FRMLFl9{H) zTsMW+>y^hh_T%TSP*8-<+g`6!16Wa8D1bCqNiXSg$*;e_s%~w}CM))ZpZmxjMqAln zukjq&gZcz%=Xdz2t=(K0)M(s`&!AB%-<}Bjc!^0=RFubbpzv9>JELUaK;f%67`@#@ zh=KD9rFKzIs^q>`Y`6&k8Z97b;lW10`Pv>s#VThn-x_~x^nu@F5u#Fs1PYQ z7#yEw)>z=Fj;dZiz)Z#o^S$cLW@Tj=SvN-tX7>UE_toKvjEs=^u&aNRkdwNZ+^O%%nAbol zCbM!E-?f(P`EAIv72zhS(vbLqulCDki1iNpLw2jf z6W0-57LO>g`qg-W9kXARaX?k|q6@c8orJ~F(INnMsYX{yEAQqYbDU=5P^5vkMQ3LwmOB|7&Cr0+drB$;i+sSXga7Tp_&x7!g>%&NMa4pTT#Gtn0k# ztYTbeln?_O0!2l1>P5uL1C8zM^Vuln2+&7`_Ab5 zZ^v4q99KRDZolf)DzQnnNR2}rh@zvPc*uTm;itRN?X*&OxV$bak*t3jx0Fr!Q4C1A zYk$J(FoZXa0CD$^VZ<5<^9L^V&Cm)T-Rkss1!PnnoCQULpIV^MmyIcT+hxH*9Iq-fjn5|d~@jm%*U!&J#uMZcX-bwum<9>OSgyg#A zR$bi_@~_q$=6Sv zJ}o&K8AZRfGn*O4_2PO--HWy9_S&2o2z;>aet&0$yN4TriKB`-%u@2$ zvFX2p5<>WnPLCO64a0xGwSUTte+*}|Xtq^U7$##&yk!y&Daue2Fqs z%N<`2>i)Uku06!1WWrxs1{}{l+XXJ3_u8-$I9T}T(f49m1xp+(ZHq-UI!>`~VHqj` z&csUWbS`y`@KIN#3l^4JKbpm#gZUYGaOACpk=Jxab!eyB1^Y8II$iRqT+D`}UoS5& z*SHVA2JgdwS=uF<3)(5l()hOH&*jJ}D-wWP~T5PLcJTyUh2> zC~d7eRY*N-=ZEri2)1YM`9SWLX9Ry66a8^wrwLh|_fnB8m^{SrDfss>pb{$YaPT=orRI5TFre7_=2FBa z8KTnvdmJ#77rb1rKeRuxW4j8GtbiyC-choQa-JagTzg6`7%F^J57E&*xB}n*`HMj0 z0yKa?@WMvFW{X=RNcoI$Rm4I(7*`Q4G5;)eep{pbhn$3j1iVCHEdr6qKr?QDcC|Zs znY_&b7Z0j;2|Uu_?a91|5S5uLoXDpFJg!xr{|{#9e|@of2lVjwkC$kj)>^?M2og3& z2_xOaylB>Mt?@W)%t5fAkZc+#w!V4e#sqjgBW@O!>9WnC#^N&;i5JERRxG@F<(fSH zmFMOBljlVYWBM(dPcWyPl|)y{{#|$fT%gr4)vv+ z8>{!L`QmunZs{o>xo+|$0W*gM4B{~g(*G?MGQJOglXy_#42`Ou@JYv+bLaMe08Wv} z&@3Xke7Pe@p8ji<9)P>M}E|P?@Hvy2UIm&cQ5O+=#^Bz0iXMXeZFZXFO23k&@SRt}|5y2vVpp1&4>H zndw?zUhI!_pb_y=`mloEdy1Y-Ww|aRXi+ac%PU98X! z5QdThlp2*ANeo|hrSX9Q01TB0<4IRaR)CZNh2rQo!j_k*ccr7tilKDwzI7T~M2Eb# ziQ4it=oHL9=%LYF&jcf^cR~T!gTh8)V&aV(H%^^AStlZ4*2Rgu*3V_wBBCAeOwH}= z6+O}-;!6OqpG(Ths-P6eE?;J_bRbU^Cu}Q^BsFY{uZM!_n&{5vVrI%!c1`B@kz6l) zKldxAsnQkX_EkmI4e!BlA7C@-UQ&V zIXorA#e2U!d;9Oxl(vFTa`|QHPTiFBvP)0I-Ic-mPHl2S+8ddgMGBB>dqE8X;4#da zpzT69ozzUc#KJs4l9y=AYnV2u3MQc-(V7`5+r13++zOAppCdVzjrlygB5MsUG+|K_YRv{j02( zr|0=|=Q^;?7_b1q%Jz}l-=09#1+}!uId#N` zJSO^5`8pP$_b^#%sYoMZ^AkgsqY&*fP%m7frNx%mnz5}u z?6wcyg9_FSnQ$_4^4`K%qmUy8-cqxGcZSSlIK&`wJ3(HWlFuUENbJ$0N1tJO9L#XwXs)ZR-1p8=(ipgZCR4qkM>1$}ZLW0M@ zyS68CaxHI(g_tkd<@Nsk_VoAy#T}hT4<5XsVrOU1fNIT*@CQ%G%TTzytlyt?w^YR5 zzIZOS;-ZLw<>jWi{K?4Eatv4O(9zQ`;}}m(FB^`P_=O>9bO-7Rj?6?2)%)&D?_*Jk z9rjSQj+Vcum}hViBCCN=Nn+-nwQVd7y*?lFbdXx{YnmIj^L-|@L z7D7S&pZS?(9^_}JMt}jbg6sb1grw=-gZ*8i@g;d!AJZsulfqiopLRX}7)AY*wAl=< zO4vUEfG|aE-|{3R>b`-xy1Jb+k%A6-Igqc*Zbp}1h7!pMg@nT^%ygp)9^pOoe>YL3L0~z#@i@Z#j#zw&|mUbl3i$373AV9+LC2 z;{*_S)QT+h_bqK3J|WA=XlNGH-cYeDmAVe-+LrUHR@6Q~d&uzR4*qNDyq~Z^@>~oO zgl6Hx(xEg1^e||8xL4iX$XV2m=tsIu3QHWNa+TUUYPZof01pd=N3Qo<*HEL z#*``eX@Tl^rsmt*|4r`Z=n2kIpRr@wB~s)?U+y&Ud4iJO^aPQlEgXs`q_rv8HdUvp zn3Sor_SSUK3wMe#^_~xbkOr!~A(djHqBDmkI(3A9k=F;XX>33x&wm_OjNMuq0ixH( zK~~+*1!PK@JoliuqN*DFdGF2Jw+T+o%@-Y^unHWTPuUpP&f0XGiWY=b?FwfOe*bpw z)j0qteMA59`Z)Xk-CWy7!DcV zAt8Q#b?rfLXn97RdKu$cXDn(Uju?at>F(4`|4H8r0+3@g5S;#`Z-R|LmU>e0S?q1S zYJN~Aaei!@;AAn>;Ur%k$!;{Lf2z0+AhrHNnnt8uyD}>jM}7sDo3L0a-+zn!bIIk`74|(XaS}r)Ud{Y;}~&Iavlq zMu43TY8%TUXc4|wzrTxxoTs{LzA(V2n`bum8>yaKr#f@8S}Affx>4*{2h(#wG3I&y9!*O+9!+Nu%Mxovt)XO<^jM@FyC zt?t{}j?3Q{L}KP7S7Tgq6y#KXiuz1H!+B|#Rsbn@JJ{(wCdEv6a|%hqUkigJi>0f- zfcvXg8?S@Fl=_u_t`xfYVzd zA4x}QpzuvrCx0&Lkwaq8oqo$-8>lc}-Fo`y!;8|7O^sQ&_WO;C=*4`rTx!HY16TC6CMY^;t@bZwTT*==*2!b)8kzLF!aXAjvJ6pXw{ zm*oEilV7AU-v#~$z(cw7ezCeB2vi`_bXovk8Ht$)!SbxX1s4Yd@1WdN2&wl?n`2PT z+IY1BX4uZXMz_|pGeb+m*;b9WJ`$vc+nDBi&Y%*VSen=k(JZl%X1hG+mlWlGWtXWH zy}+*PiOzFAu((BTdHU3;gY`@o6eEuKI(ycrBgyCO=!9lzEL9m5wrK1i>-VTz!y)I- z4fkf76J}!s{tsFwNrzW`Ys1XX~sA9Jg0o4tKGgDbjD->VO&=_m7{+s--D`LIFsIfE65D3uM>gH~2;MLfZrb zuHpXMQy`^OlEL|^As1D&<7yMHMH+x|vIgL`Sc#`8%}Sp!cXMD-&XWdjVg0Qr~NEiVX^*`C;Dt zQb_oga%T6;fYf47sQ`6ow_E2l^6y-ycFsFM*Qx?C`W$IH*Ap|oB+ez^9@-q#F(Do< zfUyAe0nX_PwdyuZi02l$hyKuC6EIk8oZ3&xS=cA6*6-l(Vr1(5Q<);cB(!A@4DHlw z2DHl`U|7e-qwX^Nz3XT)fHfU$LvbE|PbYu_d z<{P&|ACC?hB=O|Wup7%%8<8#sg&!LTSy*YMd$X|d`i^D5bJ50%Y~!?p0IO_VJ>_7$ z8Or>y(Nz+q$T8od?0%n7qXzK#=Y4qEc@#v1DoTe1gjF{%5G6HI6;w+8^5n1p?dhD4^EcrK$6AUP8X&C2Ou`2E~rs z*%Q{vLghxOe%+3ZglWfFROBVe2uO*yZM?(@Ev9!i=EFimS?}Mxc1`5BcM8Wl(cVNb z%bk>WH6YQdV_%{4!MbVwTwAkZ3GxMf?wJsh89m!(tfb!!>E}O#V+yXtC}sK(R4BHX}p^% z8MsX6Am94Nbl&)e5R}=}x!3H$0o^k~@xLrf0W~3sQ)l+;nENjnxVV(KumAmU!i%vDRVoJ)cfUi#zgH|)G!804o1z?8^Vjt`=h5hqXt)gM`WJgzzW`A11-B)l=Ej= z;e#QEMN}gRMS_H=jK!U}xH!i1(~Bei+Fk&hX4r{(5xkBrc+Y z2=`V+D3o2B!ueKHGs{dUtb?7BpxBQ7fMU#j79=ud{|RwN6kN zT@8{KHb`xp8a&*xAnzBHlP8Rn1O)5UOZp2WAZnJ31?gZo( z9}FRDcQ>IVM?5oMM}JFKj{DvExLe8C!JXS<1X3DZrN3HIgtPcgr)qU%Z%TIc4lD_o zM&AUS{wS(sLV(}d;Ww=flgvOQ!zgXLo&P6D_ZVo%cO11B*irhO(Th&yOuPSB%fS@Z zLlmrqcR?5a8|B`_9@;C}wcYa?hpGTT|K+mZJDD#feUuxQ-z)0`gS05tI z)FB}&l*k33V$HU?uLVLhC?)v&{#ROTMENjtvNzP~B*;#c8{1aEDqw0(C0y*4XG^{C zds{n#SYXLjhztU#Jm{YjJ_Qx!9<*js`IJjVwgVB^cz;}^%`Mqt@xv@eIn*q(Z(@ua z3m82rUJsb&3X|njpd51T_lpW6kkgwzA3Az&d~z-kid=C9pAz{t9cmAf=@f!b4AUT~ z-s5LDEAhmy*Ug!U3hJC0Lr~|`OS(u)OG`@Hoghj2LerUEVPX4mxk4mZiTkbWph)^| zHR{K!#gp7vQ%aQnj(z$s>D6N0)dQrAaW6_qFjpx%~mZu2Ps-a_vvxU88yj(gvlM!A^<|COzNPTDlbNU7KVk2xf9DGMjST3zn^guYd%!oStwbYXpTAr!MjSDY8GUTxH3|?8vB!YpY}qZ?OvY6 z4=?Rrl$RRq`fV~Iqo04<;T^DpbhvV!GmdCe)s38(lDENs(?Lqz8|zu#2+0OAXdDOy z^{y5>D8+!_zo$nvuh^iOrdR5@X1gE*6BF%SbCCwYLv-Rh*<(6EDPX2k|7yGni?Ud2 zDa4I}eo<6NIXcy{{pUiDfDm=!CH1CJM%6sCyMB=GYP!pTsP^kQ!bqnLwW81V0aswp zo40ad*^~GEl7OS_gIrjDObaMp5X3^s7W4z+s9u4iBw&&+3@y5dO+*IC)AB9?BTMrR z5L10)Yqn=tdL&1xh77Vbr2P7if7V}gf;1nDSvJG_(i6L1{TfBTfS%D#Mu99^y{tmY+y`ro?Cwr5+)X|5$pynN>@ssH3>? z-*|%VSAzfL2@H4orYV zfR6QLwpqc%e&i1+8n70MmAAxr#G zTid@kq7O^$@YnA~ocGrkErvV-B<>t{?-l~<+ExW=(z@tn_(~)IgmQQFu0}(9qP#iw zI3d%k3L5F^+}swOwM{&-A)RcKO>0+Jl)JIJ<93HMc|Xney|d!{8^4$&WxI@=+IBot zUA}{SdiP=K!S@NOG#mL+ZHs(f$9zgV@b)5ck7db!%lEvDJ6hfLr}4c)EdQ~(MYF4x z41ZR47JJs=KmC07tGsaAe?UPO^=@>Zy=~%>OXFDYpjfQHT4O;N85zxs_S+bs zTU}id>uhpuVYbt4k9WR@Py9m{t#7mMo+A44WKsbjA`lC}2=U*zA!={ZsRe?z6~Kfj zB8dXVeumKfFUn^gv_;!qD7KjoeRt`_2DDLS?*OKT{-tj)(J9L3J3!C@(q{Ey=k+3% zYFv7eE`wfWLoWUPgq)#u=VFE(4{$i`V5Jhfkk#Az>3kZusXq%O9l6)&&*ZVHD6l6V zoPJFdBk!CJz_%kV`rS74Y)qLQ2wP2g7nioSwpu*v41(eK2Y)?8Ln@^KJR(pTjf+}U z6;vvjpfmTTQZMV56J>xctT3)Dwp==Sh1QHRwfm)|srx-1FWL8jm}eUK#+aVBJ7lHy z02CaA??vj3Wf}K!Z0|SJAC4nkS}nF1?1}F?md5kQ{w740UI2nfhMU%O%b0gb8FoJ^YTo~qCyiH1d577& zydN>=F3D%Eex}DcPO9ge%DrrZq;bwaMJ}s|jc^0gy3!T26X(Kdt^tOkeU)|^i{$N8p?v#*wy^6LIHy~x$C0AILg83{Ht*-Jl5k3iG zTeg_Z)Gjw_kB+%G-ALoKCf_S%6SM-&gQ`x>vi3>X{RP{$4?j=nbFqvkM!DzhxNC%T zTQ3f2)kJeJ`McNkrFN~Z3_P*ica5I+Z4yp(cu`nSHRFc+45KP)jFc*M%AoAn-4Tmz1%~YhcbCU zfu#)0FuOaTSGV)C?8|s2pBL(`Zdn&?oAFkMxw2X?) z#^nP+*0-mKQdF~I1>|bZBH4>vuHI6;_9bjcSZIQv6zPd_(Xl?yQ(}mI-{n|1k~3JT z0gm+hiY#xVq1bQv{q1Un^ZOg5rv@D&)n~QU(3E@U;rfJz#o#)_?{vqC?sH??HI9Bfa-q&9KihcW~$x}lkO}yd^_WS z6ToHo$*e-tOH=EMG0%}P=#yiQNPa}R9%ROVUvF{7tF&^c%mLEO=UclE`62V6GO(7M zdOZpB`6{vc7feP2kc4+QP$b)82iUw&f zox({cy;b}9=B8_2vSRsE#ub0aILzd*{+ipg;&aQ!aVGLi=vQe_oq7AF*v0)ExenQ- zdp~l8TSJDbH%hhwAWik;bW+HjqY2bp+uk6WNNV21QF8|ilH8xLq5D@h9^cox(_|JQ zl|Sfa%j7rDepFnSf|B~m!ZGn_EmPfJOY2f<*m`|>N*KUBLZ6()TJBClmksg z#Nh-)u=)h;FZtA+C=1LnPnyQ7FUPQouUvYm&F%5R!gkfJy;kFd_|L$7KUJ>FTWq$! zzkdV*2)eCmR{OsVR?i$j8*FT+iy8l~8LENkCldr|J;Jw&rdH=EZ+%W%=zKFHIq2e+ zo)N5kj{iu7z2ZyC{QALNq*Wp;PgL3vqGI<{;N?@=uALFzaY7`!bSs#2T5Cgfd&vT-C@A&^`TOe$ z5F-%YmYJn15|GB;>3YsYs%Wg3IA6$!#5E<{WM^N18W#lu@r`jr;Z5Q@8C|Y*FA%Jo z3>3E7L6`fr0)}rAcHE+x8hjBt8h1=;*)Xqx&GEeq?L=ZET#Qx}amh!$Nx0T8PS}}6 zjIXVMJ+Jxj^5$1X(P#`B#qj zxg0Z2A{D)K9r^SP_^MC9Y2@bSlA`TmUKV6LlMWk#ro?B`)x}n`BvIbi#!FjCnU%pm zxV`ZhJags@NhYI_LCr!UNiAdOrIOdbKbN|mfi9?gPyqDurC34>kuKWKW?_fmrjTj~ z$k?`30;4>=Ui8P9)c&H@ryMm!crZh$V^>^rp`%E#|NK0I0;P_vR87SgiW#ynsaLaJ`RP08UP5?K!EM6CZ$(n^7ClbAN)l1_ zF#mwwr%Zo#sc`GA4lhmzbsKc}->#o6$~6o~`Xb zmdt&+o#cbT(;HMP1K;>&Cmp&PHkzu#c2_VzfM0U-p9U2GBz&C8ySMoGFs-qVYZMR% zkD$uP^s%BzgS+|hoM|urKu`}`2C&S)OaNp~*RD7K-q3ygAGtfebG2!4pk`Jkj%iA%@EkQ)0l*HS*1d2d6h83H+f!5u7z~BzH2kzZrWo7N} z0ZMG)-ePGEkj*mQxRH_qM^u zh7M>zLSOD7{`IzSM#iRCKJPh-v9J2?&AH=~9I%3eJGN#QY%(`{9ynj`6MI4k(rXHS zD??~j>R5rE7U+H*1kF=g*cQ~GybocS*cp6yV;@p3M$hTanUZ2$^0P_+9gt&p1x@7MMk$BjJz-fBz0BC;)xJ zrd6!eO!$Nl%3vl%Aq`gswaZE2#mEjj5y&ABoHa@DCtdeuVnXCfk%@#20st(?w}v-`qa#-7(l!zj3o>Y}0i ztdHy8zTo-*EIvj?K4OKeq{M1o2fAF~1d@9K0(($>SvuGyAc5A3|4GS4qJauPxyndN zQVQ6agU}GyEej0JY$LP{sCShjmMP3Z?s{bX;NFzyF#Wf6rdl`^XflSqpDzCWZbWRu zgleN-15cWWnqR4^;@ss#*(e%nYHBjFLTD>PSs!b+%2yh0?$p3rQrzNJ~=EO_^> z-LG)Ils(vDP*ctZkr+FapCgYr_#UdqjZ_Bj+})q=ic3b@f$+7^`_Yg(@s19hX~E#-6eIo}AEj6Jkw_sDQmt z*rJ8;93CS9;Z>WP^$ixGo0?r37 zJ>PMDtxT=aSdEI4k^VE+V<5a;YrYuHZ_NrX#Iq5U$Lk;+tnmmA;M^>fw1#jpp7xc-&7UmN@Qr|#UjgNKI)jcqNZ zzZ0dV^>s1>)A4gBG(hr8bl3AZxD>EI$0A$OU2HLh4S^*l&;kl^p*c#^v7!<*F=i$6 z`M3iWR-h9M*MA0z18n|5C&Xllz+1^en52$OZEYfCJhxyDPRkV2fJB!vrzgb6PoX@T z-?JP$AWQ%cI8v3sgn3t>@M7Ex@l?;Da-%zMi77LCfp`rMeis^ zvH46BOCC-MMlS7uZ`;nKR^!s-{^^wL5B+aVH^u@pV|cb>k?>fFI>1lMRh9<4@7hT#;l<^iu;D z<1;zWQR^fMk!t2;3sog`7nU2O8)I*k_Co0R+=R#c0B*mKJ6c8!?Z1=PZ3+xK6@Hgz z@gW{V8hiw6pqWE(XJhRkG4?;3OxA7%v6XB%4{v5w@ba9DHiSNa!mjTM&OGq{|SAhfQSHQ0DOxm5#@f-HwLZx-Y)m7rv+Gq zlpCD3Pw65apTgip%gJRqC}^gAe<^4qNfl%$Gk3~^a|Qnf%r(NB9!C{0m(~#mgR?}4 zcme?Y7tTf?_#mbsT&s?!4S@MyxN{lKBH>~^^VKd5vnDuLO6bXPPsJTb7fy>)F~OGF zJi0R+jB)!2&L58)fvCHP%iu4gkBtIx4cDWBkh44Xw+L4%T(5`7$m659jzCxd2tpv- zNRLRUY(22nz1+8}2E^ueOT%zp z3#Br|E1WiN^t{m0=SGScayaA-rVk!`8^R@regnwi69A=JO#Op&t7~B) zt`o`$Fs^&&dm4MgwW-z`C&yv(Anpg8h{|TL8*>IEbZwl@bA;AED6ef!wbIYt=xr=0 z75SGcEID(?x?2mR$_>Sk?%`e_`hO2&JB=1r@ZAg3B>rkbAP(t}Ppf)C&-6+Z`ccb^ z;Zo4(y>oM`Z_&e+JJ}aK+J{~VoKWy(w<#O|hihbbcHGF|PI&cJiwgM7o>Ubi`3I}H zo;!SeYFE^$^`QU>N32ENvo!_z7LCK)9Tq5*N`m)>({(Cl`#~rUcgxseMIbiLK1L5i zS%iZIP7~w(d?iE)EmHLoO6|KF^PrhbQ&D?70+>y5iRk?K1#HD3XS0B=YG)}F0b_cD z3ezrz;^C5^%6HP}r-NC<6zq=K4-^)6uL_jU^QUr09PY;)ZPs(M9|(%nfr6Z^PH=*$!-PoMY5HUiNZN1syM-7Q|snKQUSU<{8l5E zUV1I?C>s>RX+98@VLvN4N^Zu6`Ck+OxF%zQO@hS!c_1_{=tTPh&m7dC@Vy5sHSm1E;=;)Li&i zuYuCysZt;t0Vit1ktI%z5V47kWNP|mQt5c~AQNWIIT!&Lx|u`83v z5%dV;JNS!;sHv{5VQ>nk9f?)AOXnX8Z^8*AT4GIy%pwUe!>(KUU%`RQ& zIbHLG!(ati&j2zNC?o<-VgDc9eR({TZ`(eV3Y92ALTQveq_Xe36tb_Cok~L(*-F-A znNVcO64^$SG?rvZ_AN_fXN)al56Qge&GS5;-}m!--#^}e-}^6&&&=HSbzSFmp2v9{ z$7T81tN^?Iqa5QZEu)by^QI)M+GMhXvC+aS_NIOt?T;t6mSaUK_WLQ28uA#3lWm`- zut&4L`fyJDZ#}vu@EYso#*4dYY`wjl3L}ggoy_{{&GX}kTtG`DoH-hJ$^T(DtjgCy zxu?+`&851clEM3PS1<6U@`PNWIC4wOz#Jp4f$9g6qE&u`9i0)FBq=a_6abwpU|C!= zzSwy*y~g6C&A|ER&0lQPE<9XwrVe44gNiJx(MG>w!!p_kd~5C%J2M7~6)5@ZTYTZZ z_=jP4?WVxmeFxHlgg%d%c_p9b(@pZ~mEp{Xq-0fH{?nc}9)k2Xv0+D?eSP>KsP8GW zJIQCFqeer3tq+9g9F2+(@b#wk6MpFc6G1;LEX->rJ4;IQ23CR0%S}NshX@{S>2fHB z$1#knSOB#GXzrkwPsMyyjc;qpvk)*d%oRd2mH<-D=O$Mej1Js*XzM4^R70{5HJ+rPH{CY!iMNBp~ySVX$n zHZMcOV9#NaC;qT}!=PI+0D`^$^HW-ONs5xye1p$`pVpGs9aDEB_>jIX&)0p-|AL!m9ej8d){$ zQ4o^kzLq=yV8YDIOn<+LaczC090_V$sD&#Y)3GH>JaDWDDyUrg;u<%#c^g6`I1nJ) z+K>oXeP*jq9i^b~W(D&u(WSuUd9RUTk@)m)m1`m85v?5}r(VRx!C)_fkZ@@nwkV(! znwpuh&8<#QNxgSzF84=YYq>vZ^R?>`Mg7^OJxsWAe+_=g{eMXr5%1^O*1G~asDf=L zsA@sB(f`506+#Y7Ho%Fvg@mqUZUG|EKt3U8#?0mlxDV_LNZkW`oFa{bei0)eApAU& zlRIG!eF=_B&PJZSV4D(5$KgAVCo&yB{^9Q6rsVKOg!4)ws&xAbbgS52S3WRaAr1S1 zz&lluf?~m8rQDxpaw_X{a=K0C$JP!|!LW*0tNHr+B7|LX(IsXn&jom@k1LYP{q2|{ zQck#B{m1Pjm&^G?2XFiy_VWXuj=Ev>oc#PjKzbmHuQ|Dj1B~lQr9Wq0X{ia#76^;0 zPnz3>^78TV@$gVvfZYhNRIU#_Tsuucam7Z_f-^HAfi?ab_-4j+k|zGxjjrF)ZW;a8 zqyS7)85|&sTHxKoo<0pAw8ACBK($iUFBsSe1|L6>c-!53qDB}*3e0$`4dx%{|y_e|6fRuJ^)d zKkAn@Hnr#Ar@QjUqQJnRL*F^i9HnUYAvsqa5ao^#45hKwl&sH`O?|ud}IEK z%ujGv1d9WHc{w^ z-@#1V8!twYjx`YZL-4ZT-2Ap@{72~nCrFzRI=WTWd2Du z!rVO`&y~CoKFvbAXe?=cSKw@?k@6=Mr_Q4{@8B|6xy?*566&2!>iw(_MoX!JGGdz* z9E46EH~(7Kua8X@c1X%czF)yYo&t>XG=a*|$q9;3y!Qs$Rh3DzfX8}D;|DO2SAk5! z;0!HYPXH@%pT@!t1-2$6B#bBnwFHt3?PMvIEl;=)s8)nLeIO;+X{XKxoe)h_{#x+y z?vt0qFiUhrd-rmPYj}A2Q^C?Hu-Hq-bH%@GVPLFGtuA>@86@Xrc$|zLa}wG6?hzkL zR?RnY?mWyS)g*9<2(gALmQQ1r_7o+?`gTI%e0kXW`@jiB&$^ZH!1c6KB19$zhrBbl zB6{B1yJ9Z7d^*Bqm3dD4f|i$7zve+@!@|%${X;z6_;~)iHTAScU10I|<96g<5ZZzr z$#!wC1!!E2`j((0q3JQ*TaXftgQ+{kh0;RiCfrxArJs+ova%=ZbE-y7p^A*i7!v`L z4{BsU*&0c{zsrjD;iofSH8wQ`Jj1<*oGBAD1LQ5D>}nj$TyQK-2foA=5~wrjkF)3F z!QeMLJUACu>ImsHa9TP8Jsp{2xCUom4R2*0x7m7ta*~$5O79NTf3Um(w5f3*np4qG zA%mX;j0r~P=rmBG3cZX5+dhgue4 zW!Atl@YOezZ)_n#5;m$#ae;7Y&(#z-`@Lakhw6dtHFIq_e0ks~E@&3FVO(Rp9jYJrhbsPDnYim@JwFs$c6g^HroR$K ztgINy*?j7HX6K(De}PEVI%){W_g-bWHRpdLOMPg(+tJa)Wr8zf!3|Fw@ z`)aOy%P4tr-Hq|&tbNPC^*hyrA`o)}&+Tl)I17o?n&z8-Q@rIYUX`^)r9K=DJkgkd z=ue}+;@lFz6;qQA@#=h&OCS;}!9J=gx~LH7n4R$%$JA10`-^jv>#X|`0ZV>UjCQ6+ z0~!yyM_KYln@w`u5X;uFU?M--f5qH9CTcHW>t`zvYb5ZUc_mYz;~Hy3p{^s+*MucJ z9R6b=FJA=-LFc0=YEQ5!N5PXHAD`K0chuYbe${jAHuW+@~Ai2f1AYhb;SCDHso; z*eX|hOzVU~kqrQtM#bR^cE>;g%X(*GVM%9gZK~sTZ3sQmgedS`&4r9iO_8e1%%i1^ zRbrLJCfd-*$P+XWO(NR7qEJYTyoU)k``e54w6|byppvHh18PErb#GZ(3txvZZ#y_t zLho`I8ht?Y^uG9rmB@VhCqZfS_^Zk3_`}|`o$Pyfht6LQeXph_&Uogz**m^%pUd-I zkM%`~hu4%jnj-p)34UCY~x&5zm7wl2ygQl3=Q7>eO-PW&sdjk8c+$4cg z^IH>}0J)^K12>vEuru|*z@(ubOGqCOi$|?GvB=EK`4URSD2->Jg1B0W)0z^Yo3Z$n zjkT+Ddqzb_YOhIBGP%O)9UiFUQtn0qG;Q&l?Dl;Ni)4O%=q#~s=p>Xtj95xM#ul&g z<^CPRp}3U~>P9(*CMeq8j>VI{1?L>QQDzSl)Rq(byVcWr8J@B4({j2H?yBXr6VM7H zdYTV+a}Nk0@$3@>#`-Cl0=EGtJtP~1j1oVxNH0X%Br)`GUM4|YD}ZgXx9FA(d21Qk zGP%FE7lLI$KbWDx?+wN*TTn9X`ziZp?)`GBq>zJ!+z^5tgU{~`lJ3y^5CCqUkf{|^ zo;D{zf_lU^##iX+-lP-lVuqo&-UOAhE{%g1H-9?cdWXJZ?0MBV)lHpIJJ~2 z(toegbJr-=g|g=cak@1Q0n}$;U;vE#5_qHxb~UM@TJGw@2(><@td)21;gvS&-vfXs z*q9w=zF^gpXUyTdo+@~jleQMBUv-BfcC2e)&HOJrvTbxnJuia zmjuxiE5RY<*?vV%Ip@?R0L{omaaY1=|6 zr+Cc>ORw9J?AO(;7tdwmeq?g$uB)oD-rnTVNxWoo!(9M&mT*%}j5CJGrpM0n@?x0` zLj97S;|s=`f7{}7CQ^jWZ&%5to=mD`7UM07iW(<4oc0tTVdqod?_b*DG$(+|Lxqgd zQx+GqpV9);_nyDIal1H-9+%$2xHCh_64bs*7Uw>HTVJe{et_~j$06zeHc;^iU0>-^ zGInhCerUTe?32s>8QtJBDNl`F&gcl}gDOnkI3o%$vJqHdv;@M)%9Nu0l38=0SGqkIVJ&r3uWcGfG zp}9f4I@+F;g}^>UBvGZGbCb6-Lxc_{@h(2#ht6?~-T*9T-bHc6QxbF>GF$D6bc2Ag zpg~>tN8>9YJ@~~P?^`5fbF5zR|KDV-}j4;0w%{K`vQA@R#_W+|?-#w5#5xF8D%^V(B z4~R*Cc@<%f#)tnprtJ*4UT&px{8i&%vu1)FEURS7FO#;{EGc^w3d)r@XcTC+Q(Kfd zzGWb&O>~wGc`sk($to@F*T3ecSDU3s1muZ^hDPx7`WSSIBhXho6v z8ZXIwc{x~naf6s5WcCS3WWcW-it4P-gDS=27a!A0ULW_oI{L!jv zj{~LgC+DGZ@gMBO>cf5F6y|}sX6(mJ(uq_Ot!%c<9YtTI>ZNzb_iAMHiG=~y)8?hH zyY?n6KD+0wC{ekBA>ed1L^IiGx2XrO7`m-G^PdeLe=}JW)h!!j%KyxNyno)IA*R3f zD`Ouv=%2%Uhtn!(n6biIK>NbV|1idK9i5?dmC9y?6J%ojyM-1>^n@A|rg@ffyd_GR z0N}Q&pVi3t$)CQwG?~V{^ng{`h*cXxo%|<3py{^(Gc1jo4lU9S{bC0c&>W zh4D&AL?A`4PdIS}OkKNNzJ@V+H9>axdN~5enxiDHK!P4(2ZorGHL$vJS=n2{jmFrV zk%?)4m1b}&oD8BSQDeEi^kRe=b6R$$-68+IJ48Y7k^xKt5!32=LX07{#S2=L8{yQ_ zuo|`5)W^7|%sUECopbY=Pq6H4&sM)9R5m9Ps#+DPQoMItC4@zy=Koo|u~{wPdI52~ z0_x4r3)QqX_tq_&$2hnwpv7ifWg3vJy1RtKglYC5d1pC^7j10?rJBg?2KUf&b%Nx; zlSQtObQxxJFq@N+L*SR_?` za(a%r{`mfKM#}13pB_1G1lAzpdsHsS!^3jX{`!JIG}>bNu-65xWGQ3Y;+Yvoe749D zRLqn)qWOTwy>L>9ffYH3q}2jg5_scsh(a z6;!MNMickr>>b8C6IW98@T7#LOHV&Ez!#G}DDJuNT4o%?2N=MW@{5f;%wxQva*m=(*djRyUW`;;xlAVUJ>(~>-AH= zX+48p5$8R*%dTK337^FvJxSDZuM4)aQSVPzrOVF9(!jBNmt~Ur|L1}^-c%R=vv!0d zzr!eBp`^3hFRjHUL`6Wq#O0U68O4>ZZEb}w;TGsbD`w)$mAIo}xLZz4|0pl`gkCc= zMQmS_X}>x7E;nv~wAm@oh{tp`z*K(z;>9`FoPmcMWbj!GFo(h%3`6%U0TB)EMb|#0=Fxk@#L+Z%R~S3P19gHbdT;g}3dtZl+-E+co3B zpedE>g^F1^MY)>iNvN_R#ydkGbTKvMhM`XbunqJhmrJ-ky=-^m06KDy@nSGJ@U)k* zZiwLNNuS7}>@RP1(k0yI&j0QmppWL;`_6%mCck5O4=;oP2nNj^OJsXpIZW{G*uTV3 z_U1=V|H*uHxAPwLK61~wrzY28%|tW(_&vGZ#?3lLZX>bX977pvfTZ*DaX037>&YpD z(#>lh@Mbf7kj>Wz`u9yyidLYWq~-o#?U67My?o=9O9_I>jW*DB1xht*ULFKa*J73o zATc1GF9ebEf=Y#^MBk%!BB44bH}{R)-;x)5aqnYdV`F1to|M?5CxAHVxFvm|2D%Jy ze%KGAyRUl;Ltt#Atg|4z&C24L=|v8^$jRa565-Bw)fE#HTdD&d9_ktXf!#ncprR)i z&!DRQN~0&~48&-hUL1Vy-jzYhQAI=m{QMWU-(B@_7%{avwI@eLMSid$#|CU}0j)!O zP?VniHS|p8e09-jp`&`NH7#O&Z!&!EJ+rG@iKOa%k{n}qdh~@OJda*8foX^5UNI`< zY+9#T)Sa}9t)vWJ`V0C&FOE`{hNV6V&e88Y((`GsQ_kYc8AZ&ax(9IYV`H}}GPCpY z%!;f-pG3mpAGyH#eEnK3t)Y+f_PM@TR&_q%B;JQTaTGuSKcFj4 z!plPOf;OEOi5*&4LKCdKW-sX0ceNx-|3Rp<#D42e&)SbganF?zoENU6`j(MDdeBJQ z9j>VEnOXXuCjUNbp~%EX`j%-E(u9Xg&LFoy)!9iLxgWUd9amRF?g;#wkZ+v-nY}Qd zfZQ@%oVne-Tpx>p+`99tb@8TG?Z>!(9n9dLXYjxN(0FJ|85n4YLu*#VW9L2nZ{(Sl zXtNjQ=HxV=;wh3@vf~wU0s0qDR$5y1!0z4Y>cA;JDJf(CoD(E#c4I%wX2x5RK(eCN&NG;(rsX0{h8f1UctTq#t8gXni>^|dm{{Le? zx0K7-*t}r_n{)2lk~_Cs?|_)2sBXifSWpq&85QxjFe*Qe?7WON0g8&R?=~0@ungHx zQYisx2?%$<+WfQo5A{A!bXCsc%38&Vi7+Hw)KGC-kp&YEr^>YawPy+W#@;Bmj9Z{_Mf-u#+rLt TANR$(BCn_`Yb)UttRDXtY4jn_ literal 0 HcmV?d00001 diff --git a/map-reduce/etc/map-reduce.urm.puml b/map-reduce/etc/map-reduce.urm.puml new file mode 100644 index 000000000000..508f1fd3363d --- /dev/null +++ b/map-reduce/etc/map-reduce.urm.puml @@ -0,0 +1,24 @@ +@startuml +package com.iluwatar { + class Main { + + Main() + + main(args : String[]) {static} + } + class MapReduce { + + MapReduce() + + mapReduce(inputs : List) : List> {static} + } + class Mapper { + + Mapper() + + map(input : String) : Map {static} + } + class Reducer { + + Reducer() + + reduce(grouped : Map>) : List> {static} + } + class Shuffler { + + Shuffler() + + shuffleAndSort(mapped : List>) : Map> {static} + } +} +@enduml \ No newline at end of file diff --git a/map-reduce/pom.xml b/map-reduce/pom.xml new file mode 100644 index 000000000000..4778f1bdb7ca --- /dev/null +++ b/map-reduce/pom.xml @@ -0,0 +1,62 @@ + + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.26.0-SNAPSHOT + + map-reduce + + + org.junit.jupiter + junit-jupiter-engine + test + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.mapreduce.Main + + + + + + + + + diff --git a/map-reduce/src/main/java/com/iluwatar/Main.java b/map-reduce/src/main/java/com/iluwatar/Main.java new file mode 100644 index 000000000000..cdbc809ae381 --- /dev/null +++ b/map-reduce/src/main/java/com/iluwatar/Main.java @@ -0,0 +1,55 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.logging.Logger; + +/** + * The Main class serves as the entry point for executing the MapReduce program. + * It processes a list of text inputs, applies the MapReduce pattern, and prints the results. + */ +public class Main { + private static final Logger logger = Logger.getLogger(Main.class.getName()); + /** + * The main method initiates the MapReduce process and displays the word count results. + * + * @param args Command-line arguments (not used). + */ + public static void main(String[] args) { + List inputs = Arrays.asList( + "Hello world hello", + "MapReduce is fun", + "Hello from the other side", + "Hello world" + ); + List> result = MapReduce.mapReduce(inputs); + for (Map.Entry entry : result) { + logger.info(entry.getKey() + ": " + entry.getValue()); + } + } +} \ No newline at end of file diff --git a/map-reduce/src/main/java/com/iluwatar/MapReduce.java b/map-reduce/src/main/java/com/iluwatar/MapReduce.java new file mode 100644 index 000000000000..86964d5dbd48 --- /dev/null +++ b/map-reduce/src/main/java/com/iluwatar/MapReduce.java @@ -0,0 +1,55 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * The MapReduce class orchestrates the MapReduce process, + * calling the Mapper, Shuffler, and Reducer components. + */ +public class MapReduce { + private MapReduce() { + throw new UnsupportedOperationException("MapReduce is a utility class and cannot be instantiated."); + } + /** + * Executes the MapReduce process on the given list of input strings. + * + * @param inputs List of input strings to be processed. + * @return A list of word counts sorted in descending order. + */ + public static List> mapReduce(List inputs) { + List> mapped = new ArrayList<>(); + for (String input : inputs) { + mapped.add(Mapper.map(input)); + } + + Map> grouped = Shuffler.shuffleAndSort(mapped); + + return Reducer.reduce(grouped); + } +} diff --git a/map-reduce/src/main/java/com/iluwatar/Mapper.java b/map-reduce/src/main/java/com/iluwatar/Mapper.java new file mode 100644 index 000000000000..5a21a73f0d4a --- /dev/null +++ b/map-reduce/src/main/java/com/iluwatar/Mapper.java @@ -0,0 +1,56 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar; + +import java.util.HashMap; +import java.util.Map; + + +/** + * The Mapper class is responsible for processing an input string + * and generating a map of word occurrences. + */ +public class Mapper { + private Mapper() { + throw new UnsupportedOperationException("Mapper is a utility class and cannot be instantiated."); + } + /** + * Splits a given input string into words and counts their occurrences. + * + * @param input The input string to be mapped. + * @return A map where keys are words and values are their respective counts. + */ + public static Map map(String input) { + Map wordCount = new HashMap<>(); + String[] words = input.split("\\s+"); + for (String word : words) { + word = word.toLowerCase().replaceAll("[^a-z]", ""); + if (!word.isEmpty()) { + wordCount.put(word, wordCount.getOrDefault(word, 0) + 1); + } + } + return wordCount; + } +} diff --git a/map-reduce/src/main/java/com/iluwatar/Reducer.java b/map-reduce/src/main/java/com/iluwatar/Reducer.java new file mode 100644 index 000000000000..6bd9f5f10625 --- /dev/null +++ b/map-reduce/src/main/java/com/iluwatar/Reducer.java @@ -0,0 +1,56 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * The Reducer class is responsible for aggregating word counts from the shuffled data. + */ +public class Reducer { + private Reducer() { + throw new UnsupportedOperationException("Reducer is a utility class and cannot be instantiated."); + } + /** + * Sums the occurrences of each word and sorts the results in descending order. + * + * @param grouped A map where keys are words and values are lists of their occurrences. + * @return A sorted list of word counts in descending order. + */ + public static List> reduce(Map> grouped) { + Map reduced = new HashMap<>(); + for (Map.Entry> entry : grouped.entrySet()) { + reduced.put(entry.getKey(), entry.getValue().stream().mapToInt(Integer::intValue).sum()); + } + + List> result = new ArrayList<>(reduced.entrySet()); + result.sort(Map.Entry.comparingByValue(Comparator.reverseOrder())); + return result; + } +} diff --git a/map-reduce/src/main/java/com/iluwatar/Shuffler.java b/map-reduce/src/main/java/com/iluwatar/Shuffler.java new file mode 100644 index 000000000000..7eacee034642 --- /dev/null +++ b/map-reduce/src/main/java/com/iluwatar/Shuffler.java @@ -0,0 +1,56 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * The Shuffler class is responsible for grouping word occurrences from multiple mappers. + */ +public class Shuffler { + + private Shuffler() { + throw new UnsupportedOperationException("Shuffler is a utility class and cannot be instantiated."); + } + /** + * Merges multiple word count maps into a single grouped map. + * + * @param mapped List of maps containing word counts from the mapping phase. + * @return A map where keys are words and values are lists of their occurrences across inputs. + */ + public static Map> shuffleAndSort(List> mapped) { + Map> grouped = new HashMap<>(); + for (Map map : mapped) { + for (Map.Entry entry : map.entrySet()) { + grouped.putIfAbsent(entry.getKey(), new ArrayList<>()); + grouped.get(entry.getKey()).add(entry.getValue()); + } + } + return grouped; + } +} diff --git a/map-reduce/src/test/java/com/iluwatar/MapReduceTest.java b/map-reduce/src/test/java/com/iluwatar/MapReduceTest.java new file mode 100644 index 000000000000..86dbeff5cb84 --- /dev/null +++ b/map-reduce/src/test/java/com/iluwatar/MapReduceTest.java @@ -0,0 +1,48 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar; + +import org.junit.jupiter.api.Test; +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +class MapReduceTest { + + @Test + void testMapReduce() { + List inputs = Arrays.asList( + "Hello world hello", + "MapReduce is fun", + "Hello from the other side" + ); + + List> result = MapReduce.mapReduce(inputs); + + assertEquals("hello", result.get(0).getKey()); // hello = 3 + assertEquals(3, result.get(0).getValue()); + assertEquals(1, result.get(1).getValue()); + } +} diff --git a/map-reduce/src/test/java/com/iluwatar/MapperTest.java b/map-reduce/src/test/java/com/iluwatar/MapperTest.java new file mode 100644 index 000000000000..d8011c9846f3 --- /dev/null +++ b/map-reduce/src/test/java/com/iluwatar/MapperTest.java @@ -0,0 +1,50 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar; + +import org.junit.jupiter.api.Test; +import java.util.Map; +import static org.junit.jupiter.api.Assertions.*; + +class MapperTest { + + @Test + void testMapSingleSentence() { + String input = "Hello world hello"; + Map result = Mapper.map(input); + + assertEquals(2, result.get("hello")); + assertEquals(1, result.get("world")); + } + + @Test + void testMapCaseInsensitivity() { + String input = "HeLLo WoRLd hello WORLD"; + Map result = Mapper.map(input); + + assertEquals(2, result.get("hello")); + assertEquals(2, result.get("world")); + } +} diff --git a/map-reduce/src/test/java/com/iluwatar/ReducerTest.java b/map-reduce/src/test/java/com/iluwatar/ReducerTest.java new file mode 100644 index 000000000000..ab4490b9e1a2 --- /dev/null +++ b/map-reduce/src/test/java/com/iluwatar/ReducerTest.java @@ -0,0 +1,74 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar; + +import org.junit.jupiter.api.Test; +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +class ReducerTest { + + @Test + void testReduceWithMultipleWords() { + Map> input = new HashMap<>(); + input.put("apple", Arrays.asList(2, 3, 1)); + input.put("banana", Arrays.asList(1, 1)); + input.put("cherry", List.of(4)); + + List> result = Reducer.reduce(input); + + assertEquals(3, result.size()); + assertEquals("apple", result.get(0).getKey()); + assertEquals(6, result.get(0).getValue()); + assertEquals("cherry", result.get(1).getKey()); + assertEquals(4, result.get(1).getValue()); + assertEquals("banana", result.get(2).getKey()); + assertEquals(2, result.get(2).getValue()); + } + + @Test + void testReduceWithEmptyInput() { + Map> input = new HashMap<>(); + + List> result = Reducer.reduce(input); + + assertTrue(result.isEmpty()); + } + + @Test + void testReduceWithTiedCounts() { + Map> input = new HashMap<>(); + input.put("tie1", Arrays.asList(2, 2)); + input.put("tie2", Arrays.asList(1, 3)); + + List> result = Reducer.reduce(input); + + assertEquals(2, result.size()); + assertEquals(4, result.get(0).getValue()); + assertEquals(4, result.get(1).getValue()); + // Note: The order of tie1 and tie2 is not guaranteed in case of a tie + } +} diff --git a/map-reduce/src/test/java/com/iluwatar/ShufflerTest.java b/map-reduce/src/test/java/com/iluwatar/ShufflerTest.java new file mode 100644 index 000000000000..cda651c59a2a --- /dev/null +++ b/map-reduce/src/test/java/com/iluwatar/ShufflerTest.java @@ -0,0 +1,47 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar; + +import org.junit.jupiter.api.Test; +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +class ShufflerTest { + + @Test + void testShuffleAndSort() { + List> mappedData = Arrays.asList( + Map.of("hello", 1, "world", 2), + Map.of("hello", 2, "java", 1) + ); + + Map> grouped = Shuffler.shuffleAndSort(mappedData); + + assertEquals(Arrays.asList(1, 2), grouped.get("hello")); + assertEquals(List.of(2), grouped.get("world")); + assertEquals(List.of(1), grouped.get("java")); + } +} From 11c06fc59a6339e0c75061e478a7b755b0db098d Mon Sep 17 00:00:00 2001 From: hvgh88 Date: Fri, 31 Jan 2025 12:16:47 -0500 Subject: [PATCH 2/3] Updated README.md --- map-reduce/README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/map-reduce/README.md b/map-reduce/README.md index a2eb3e9f3193..2b0b150033bd 100644 --- a/map-reduce/README.md +++ b/map-reduce/README.md @@ -25,8 +25,6 @@ MapReduce aims to process and generate large datasets with a parallel, distribut Real-world example > Imagine a large e-commerce company that wants to analyze its sales data across multiple regions. They have terabytes of transaction data stored across hundreds of servers. Using MapReduce, they can efficiently process this data to calculate total sales by product category. The Map function would process individual sales records, emitting key-value pairs of (category, sale amount). The Reduce function would then sum up all sale amounts for each category, producing the final result. -> -> In this scenario, the Abstract Factory is an interface for creating families of related furniture objects (chairs, tables, sofas). Each concrete factory (ModernFurnitureFactory, VictorianFurnitureFactory, RusticFurnitureFactory) implements the Abstract Factory interface and creates a set of products that match the specific style. This way, clients can create a whole set of modern or Victorian furniture without worrying about the details of their instantiation. This maintains a consistent style and allows easy swapping of one style of furniture for another. In plain words From 8fc6130a437a6151e82d7b721617aac24e31bf2d Mon Sep 17 00:00:00 2001 From: hvgh88 Date: Thu, 20 Feb 2025 17:10:46 -0500 Subject: [PATCH 3/3] added module to parent pom --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index 5f0d579cf78f..7e571224403e 100644 --- a/pom.xml +++ b/pom.xml @@ -224,6 +224,7 @@ money table-inheritance bloc + map-reduce