From 279ed23b1e70c179df63a9d392b425563a8d762d Mon Sep 17 00:00:00 2001 From: Benjamin Michotte Date: Thu, 28 Feb 2019 09:02:27 +0100 Subject: [PATCH 1/4] rewrite --- .editorconfig | 13 ++ .gitignore | 5 +- .prettierrc | 9 ++ .travis.yml | 20 +++ README.md | 11 +- composer.json | 40 ++++++ example/helpers.php | 112 +++++++++++++++ example/image.png | Bin 0 -> 52253 bytes example/index.php | 34 +++++ index.php | 219 ----------------------------- org/Michotte/Paths/Dijkstra.php | 241 -------------------------------- org/Michotte/Paths/Point.php | 155 -------------------- phpunit.xml | 16 +++ src/Dijkstra.php | 139 ++++++++++++++++++ src/Point.php | 38 +++++ tests/DijkstraTest.php | 63 +++++++++ tests/PointTest.php | 36 +++++ 17 files changed, 530 insertions(+), 621 deletions(-) create mode 100644 .editorconfig create mode 100644 .prettierrc create mode 100644 .travis.yml create mode 100644 composer.json create mode 100644 example/helpers.php create mode 100644 example/image.png create mode 100644 example/index.php delete mode 100644 index.php delete mode 100644 org/Michotte/Paths/Dijkstra.php delete mode 100644 org/Michotte/Paths/Point.php create mode 100644 phpunit.xml create mode 100644 src/Dijkstra.php create mode 100644 src/Point.php create mode 100644 tests/DijkstraTest.php create mode 100644 tests/PointTest.php diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..8071135 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,13 @@ +root = true + +[*] +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +indent_brace_style = 1TBS +indent_style = space +indent_size = 2 + +[**.php] +indent_size = 4 diff --git a/.gitignore b/.gitignore index 9ad2d9f..cfa94a4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ -nbproject - +vendor +/.phpunit.result.cache +/composer.lock diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..60fbb51 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,9 @@ +{ + "tabs": 4, + "semi": false, + "singleQuote": true, + "trailingComma": "es5", + "bracketSpacing": false, + "jsxBracketSameLine": true, + "arrowParens": "avoid" +} diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..c25e9b0 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,20 @@ +sudo: required + +language: php + +php: + - 7.2 + +before_install: + - sudo apt-get update + - travis_retry composer self-update + +install: + - travis_retry composer update --prefer-source $COMPOSER_FLAGS + +script: + - vendor/bin/phpunit + +branches: + only: + - master diff --git a/README.md b/README.md index 48c596a..47ef095 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,9 @@ -php5 Dijkstra -======================== +# Dijkstra algorithm implementation -This is an implementation of the Dijkstra algorithm. +[![Build Status](https://img.shields.io/travis/bmichotte/dijkstra/master.svg?style=flat-square)](https://travis-ci.org/bmichotte/dijkstra) +[![Quality Score](https://img.shields.io/scrutinizer/g/bmichotte/dijkstra.svg?style=flat-square)](https://scrutinizer-ci.com/g/bmichotte/dijkstra) -see index.php for an example \ No newline at end of file +More on the algorithm : https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm + +You can find an example on the `example` directory. The result of this example should be something like +![shortest paths](https://github.com/bmichotte/dijkstra/blob/master/example/image.png) diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..8a0e57a --- /dev/null +++ b/composer.json @@ -0,0 +1,40 @@ +{ + "name": "bmichotte/dijkstra", + "description": "php 7+ implementation of the Dijkstra algorithm", + "version": "1.0.0", + "keywords": [ + "dijkstra" + ], + "homepage": "https://github.com/bmichotte/dijkstra", + "license": "MIT", + "authors": [ + { + "name": "Benjamin Michotte", + "email": "bmichotte@gmail.com" + } + ], + "require": { + "php": "^7.2" + }, + "require-dev": { + "ext-gd": "*", + "phpunit/phpunit": "^8.0" + }, + "autoload": { + "psr-4": { + "Bmichotte\\Dijkstra\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "Bmichotte\\Dijkstra\\Test\\": "tests" + } + }, + "prefer-stable": true, + "scripts": { + "test": "vendor/bin/phpunit" + }, + "config": { + "sort-packages": true + } +} diff --git a/example/helpers.php b/example/helpers.php new file mode 100644 index 0000000..747f4e2 --- /dev/null +++ b/example/helpers.php @@ -0,0 +1,112 @@ +equals($point2)) { + continue; + } + + $distance = Dijkstra::distance($point1, $point2); + if ($distance < $minDistance) { + $point1->addPoint($point2); + } + } + + if (0 === count($point1->points)) { + findLinkBetween($minDistance * 2, $point1, $positions); + } +} + +function findFromTo(array $positions): array +{ + $from = null; + $to = null; + foreach ($positions as $point) { + $from = $from ?: $point; + $to = $to ?: $point; + + if ($point->x < $from->x && $point->y < $from->y) { + $from = $point; + } + + if ($point->x > $to->x && $point->y > $to->y) { + $to = $point; + } + } + + return [$from, $to]; +} + +function drawPaths(int $max, array $positions, Point $from, Point $to, array $shortestPath, string $filename): void +{ + // open background + $image = imagecreatetruecolor($max, $max); + $color = imagecolorallocate($image, 255, 255, 255); + imagefill($image, 0, 0, $color); + + // first run, draw lines + $color = imagecolorallocate($image, 32, 230, 200); + foreach ($positions as $point) { + foreach ($point->points as $link) { + drawLine($image, $point, $link, $color); + } + } + + // then, draw the points + $color = imagecolorallocate($image, 32, 230, 36); + foreach ($positions as $point) { + imagefilledellipse($image, $point->x, $point->y, 10, 10, $color); + } + + // draw the shortest path + $color = imagecolorallocate($image, 255, 0, 255); + for ($i = 0; $i < count($shortestPath); $i++) { + $p = $shortestPath[$i]; + if (isset($shortestPath[$i + 1])) { + $d = $shortestPath[$i + 1]; + drawLine($image, $p, $d, $color, 3); + } + } + + // and finally, draw the from and to points + $color = imagecolorallocate($image, 255, 0, 255); + imagefilledellipse($image, $from->x, $from->y, 10, 10, $color); + imagefilledellipse($image, $to->x, $to->y, 10, 10, $color); + + $content = imagepng($image, $filename); +} + +function drawLine($image, Point $point1, Point $point2, $color, int $thick = 1): void +{ + if (null === $point1 || null === $point2) { + return; + } + if (null === $point1->x || null === $point1->y) { + return; + } + if (null === $point2->x || null === $point2->y) { + return; + } + + if ($point1->x === $point2->x) { + $from = $point1->y < $point2->y ? $point1 : $point2; + $to = $point1->y > $point2->y ? $point1 : $point2; + } else { + $from = $point1->x < $point2->x ? $point1 : $point2; + $to = $point1->x > $point2->x ? $point1 : $point2; + } + + imagesetthickness($image, $thick); + + imageline($image, $from->x, $from->y, $to->x, $to->y, $color); +} diff --git a/example/image.png b/example/image.png new file mode 100644 index 0000000000000000000000000000000000000000..ee9de8598a1442a03ce1f565982da1579eef17db GIT binary patch literal 52253 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

l@`Jj$CXE&z?x3rgoNI# zW9qqmyTi?kyS4n!b7yfGnI-7TYE-MX!Wl-u#!jr~b%k$by?uQ={(s^-+ zV+qXs#uK@{KjByICwe65MjES;$b&^a{`P$52m4(8xP5=b?#w555=f{5FEdiNPd50uPKg5Z~P@)`V?#+pMcBaT0ij*EpC zCDlDHIe2ut|F&gK$@#bN>q$JI#?qp-ZJ)pMf6rq=jGSp_uKb9E>i<~ z1=fDsL^^RMAVX#`8t}3WZWvW;GdmHlBJ_fr#>(@&DeBNtUS{2S>9DX&_)o{puO*Tb zXK_tLa>$Tmd1Co3gR$RO)+?}9p=*v*5G#a#Ff`U4OM(gU6 z_$9S?q+s|}ux+&qtd?$*>hfF~`*DwGQswm1ThQ7Vk@)1n!_<|@I3tM}#z-0ylfwxl za9mwdV%`+{>*H#)=>dPi<1ISuC6OrpvDGSJhZmO&X=ZW5%5O zimu`Cnz-mmq%8#o<%r~&MMb6aq1C#t+!6vy2^6#D?FJh4RuD0CDdUE_79X{=waVBV zGjB-u);JW^mD1kZ(|4*SVOft(;B~A0E&eOEe-73yr2>z;GEWb`V4rco{IG_=T1yHv zI$ha%D~=nc4t{?=n11LZ?+hb3gclmuNmiMf1S?qxQC;TNLd3)5qPZ8@oF`2PPdI7m z&7d0n!f^C@2D-}S*RQ8v;7-gNt3`Y)uOYD5Oq~9tV%xQPD=)L&&+q(szkLsNt}d|- z86z>1*pmxrtSU)Vdsx455oD>iEKiiRwRj9&TCwYZoO;0Ivy8x6Wv3V*5|EAy^yKtb zB(u^_X}f*DY7SrCp@(9`4-^f>R zlgv&YAMM4Y)nSYyZ5G-eG&+$+e{8NypCT$-GIF}&Vdmm82BTAsY)02ch8`E}xQ!n5 zPK@H^z9q&JkX(2|9kT@n643R0E+{;azaJtgt#V%_i=yP)47jK&R`Osu)=p93=5mZT z%P@?~qG;0F;t^ShZ%L}1aSg$CT)B+8YN{*i)G$>>HZQrVv;ke|F%Q->9^l=Aolk^C z0FM{`4A{zOneL6*hy>hK?NgnyDvcR#R40~O{&hN4fv{q@u-Ib30kDwTkeox+RlCu~ zRfT@3E<7#!^DL+C&({w8RbanXHRf{reub|OdnzrX3rc-g0qbZic4RO|kc336MwOGS z_GUfaw2x=6Cl8i7bqnJM$dUqUW29sj^u(S$&!5l#5?fExScV|UH?tz2S7EjqFiTz4 zIk65_&{%kTO68&Bzv5-J)72FZx`(VN!ir`?Z_@UlYLDTws=%t<0jxC_H%6(wt<0Kp zDVd&zu^+Rh8Vy5iCyg~A@0M~R7=kDk+f`jbmsMO^-1T(-R+ZV*dI}l8m1R|dHH?Wf z7sHhW#{kb3mlLCgPH;Dsh!e|D0&~*PsM?9K3kxh+a#?s&DictYS%FzI>=N3y=`F)p1tp6Rf6~zOsCQx(c}s)) zhN3iAaH6CHNgfFgsET9oTvqU0?8XsEJ`PF?>LyZh6>AHuOG$y8%nHo9%93)r>QW7B z^q}+vkh`DC8%}X`D^;SdAe>Io@rW$(6msL+sJCV>e;W;p(m1y zv^gcG3brTAE{Bu9YgSi50xtOqPWALZ7ik~yjnl-DvbG1yU3C{LsIJV3Wr*|K3a6b| z@dMbPch;zT`PF4;K8P{f;MfkJ~wV=4yGx`IrRB~rD-2ac*4sfa+h(9I5~QyQ~H(&a*mL9`KWJVc@yjHRRziUy^i#4oUk z2g|8xP+5?z10KH@@n*r5Z@;!RT4;1!1w86+V)DOoV6!e#89}55&RXh9a=K9PsCOh6 zsFBKlR*@y72EKb}S+{uCZcZ%YU`)tB%FG@*lHwaLN69+Tf=q`> zXsp`CWTa=bocH$q>fHP7`_-XORk(o%S>=vlFg+$nZ`Fl7DZ3@PCo=(8p_ozFa%nJz zjU;a&Ifk{TUbiERfZANwAWyc|3}i6l!>vU!{CVt+>%iE!-&b&M4 z;1haX%6el*7w;NtM>mDu3o%}P;O?5J3U(7%>mn8G$Dzoq+z;nksxgZxJr4b*#Dj~H zZkE!r{fXGTaVV<#TMGr-zj5<$E-IE*koQAl<D#bQY$&iA+kJdm z9AzPqwIoDlA+2W`>uU7a+S2h>DqewDX*p$Xtv!ZVueqY~@eOkuGk@xL%FmOPN9JLA zoA(~w16GRUDUzbIUmb8Z6j<$y9FUZAb*fLl1BU`cv8gH2YL$(&@_O!>A`ZEJXJBO)RhNr-|^Pd+_nZAgBSCzyHU-wVHqR$LW)%?J7wPFZk9n}l2yTw)9)elp9D1{gN8$;XUFv6a zU~V!y9dB3TH{!=1{gUEUt?P>s__3V3eRXL}ku18W3Ee44?$Fw6)fJ^O0~nbjRm}Y# z-xNCwEJoLMoxT^!axI!8F;HmeN3aLuqQuPuE;fS2R-OHT5kSQEBqBA`3B2$wlg9ZG zS&x5jyf;HjNvLEO|JLnKtQSskxUX*Dv7%M&27g8T$#{BLzN%GeXMvStd#^V_}vA{ZKn{r)r zRmKx-I96Al+xP3-JSU*Eum?*{ujkUFZLdaSvGc3kSxi@{t770RsSMy`%h>VCYAkwb z@sY!3jMS;eEmD^dSV@)8se@~a)vT@%;DCSr^A+9~GuMS+1T1Q1%V91nIwF%WR)Vx2 z%du`9Mbs7IP>E$$@Xx?=MsG<*vnzLf73&7t`E*yofJN#O0&5Hm4}oT@Epi^`>Dys! zYd)O;A>FxV%ytg-5JPz%M&XASiM%I6Htee||CXS!iiN-|s9c5dtgPaZMQ>K}$Us`r zO=yZ>f+wAH_6dIOs|YMXlS&rCb22Y6pW;pT957?+WYAc}#=4RdQX^aD@$!iZ!Mn@f z_hclY+IE822gh7nJ^i76*NfKep6cB2KKjpG1#(_c9CDMnS(+RnPw&q)!#ySBSa_vNB-( zc`}QRpdg#F_RU(PE+nveXm*|*FQ=}y+3s9{!P07&VZ`g)^SqdINRPPBIk9{VV_g+A z?3Q|q0mtN0k0FKS1d=FsuK3jP0dZ|z6d*&cA{QY^eif*<;7EKb7(c7$BB5;buSypZ zSU9|G3Zr-R+XMn{cxdpZT!f{qD}bmcdZbeI;P(CTeECV?N$xqNh3l>O0JXF@vG^7q zYpgF$(mGO^K{uvZooIGP!7F1n$2~{mGCCI8uSbajfmcW80(qb-b z#juLG;2D?EZjaE&erl?q1duCY3vjRB_yek=T=k@P1_%H{)$|A9?+ySf8+MAWH zEUY?Rpss>6shr;ne|}r7`pNy_^7F%ZKh~5`Bc23lN$w-)y@gUT*F$o<|J>Z&vWi>0 zqaO-Qi$aSHM5cIuoNg4lLv^xZ<3NIq4HEQ$|I|Fh7*4GAe!P8uxG(1pX?4)>_=e<$ zGoB%nLB{8Fn!T^h-}gYTPtuhYR4J#_*=;*spD~!|+1EkKL3mq1Z4}L}Ytu|WPFmA5f*9SvmCF_uM7bQ=M zmlMFgSc(TLt^je2G!7*wF2``b>h#;S?KxO|q4zr5IEKydJRz0)dDS2!=9lC^7K61` zf|2Og4dRPBPf$+OSUt{LtF)5GOxIg_3yo8Hd;@;cAk~SeuFk%$UiY;Hit4Cp z{9#bNVPiz(L==CgJNL-rF_0ilXzcK<*>5f_#aj zxzBusoC|P6pcPG+I@sA<7n2V?4=mY4D#K zR)V?cNqWHhLU8`d35cqunCQpqO)y6>u5rpJEA|Mzzlhf!7TE9@(m3?yBJGkK0wZH& zft&5|44TJP^P4elK zApfnvrGcM&pAGa^xs1#bhc^ss7tqEHSPw2LumIk~PXX`TWKAzO7tTl?+`|Yu&50Uz zFp%r$ttk<%;1jrn52y@2>GSF^#(?LJ5kaMu{JG#Ld(Nw;2SGWs0oi-SY?<++-oFyY zA1kUj4h26?Dlp|-gh7#HZA zb6?=u4KDGBtl;d({u%XJope~vi3QW!p#W-}-gB-jmy%h%J<#oz-(&YB&P4?l{s5Ai z*>!*;s40&73-N++dNJY8^7^ic)( z;9$JWm{}JZU&>JOstEFKz+YCOo_pG=BiogP!k=i<%(ZEtR0uaAGfLMqZ&iBSeW2}DVJ41Ni<)`%-D;^|yIQ_7vM zXqLA%xTxHb+D4fPEE%M_*4$KjK=f8)*W{je&sgLJE2Sm}EHi)wh>9{Bb8Of~EN+Pkm<`5w$fy`(V!03ZNKL_t)o zK8P#JcSNAx#KYqBfMCm6Vmgh%iwdmV<_W$^FV)uX&biySqdW~SV8vz9Ai~pI$z$Sa zMUSZnPC$&J+RsjzL;cKN$1G@HV3=V4ZW`5%lM$TEPS?ir6|8 zl{}tsARf8IEH6l7=rEt(^A|dBH4!4lxq@R{T2GW+ zYbR~-mEJ_vdA!cT>&Wivp}LY@$(&p3c7CdRQe#1{tDEb}a>IEY-C%NDcV_lMadGi7O0HE9V zM_PDQWp}xZ@S>?M+$$M7s9@K`Ma5ziF$QA)E;kY{DzFS=7+_M!-n;6jDyLXx)uQ%7 zYBf`Q{3?LwsONJV&(aC`ap5W<%pTv$XUl}MMZr>mH1Ay2huIQk1Bdj@C7kVk(NYgSzk_jZ&VN#}G( zH2K__TmEf4P9on?AttcOblQ{{yCOT8YIA7ejF*EWCtvZ1)n?$m-(ty8Kq^~-xY$5K z!4Hv|zg)55)9nQn z^=wA4vo0sFxL(KO@h87(-K15FIk8;L4Km15k!;CX4lR10#$l8(=VimN*xpgXr3VqQ z=EU>4D*R1*$(R6cLQt#l~aVDf4u^Y6*~FyC#|mM07P2* zLISI{v2dAXBw1y7XkomzAljj9+z>fkzb;m2Jj8@KZPo6_$gKrDqx%~O>D$))qXAs# z=qlgX2*p!;JVjl>^R8krjdIF4S9$~3K07fU7ZO;;MD)xlEwoP-NP0&RdK>n0Xcb$a zrI*<$lDyM-kcOohkd7Kc{yEE-xsEtE*^(rCE4I5-&b zPG67VJ|yWR#*?l}^_=X->LjOf=YmgUc>FwHT>YPW{DjZJpR$SK(cVW_5m;<&Q86?u zEgr@^MR#a{)$4x#yn74G9cam!w6;o883hWrDB9}NNRZo}^iBmy+4ZrrUY(e;)%Y;+ zQ&;fxV;vgHMfq?+_3}=vd#9EhGs>B zk9A>RH9G{X6P-h#h2V;knN>j#wpc=A!d4EE8ukTfEcX-c3{!S>haSqu%V#k+ZUBvI z*(C(lHCG6u5#?snlE&e}$Ox7-JfIOYG&O8b4fUChDrp5gNlp1Wfy>H8f*AhYzEKp> zSV_IEq+eoAR}^V1_w0gT7VN@_WM-ThKKFj6; zi%rg*YgCc+>nd4;2TQ9l7cRc53)}$lhivp%!T~agx1)BqJ#rLu)m4z#5fVJ$El^jy ziWTKlvSPRmsG1?=$@sBcCM(Nj8ab)4+`!44&dvf$#C^51xnC8vqfrX_H2rLfY}Rm4 zEc}(*s11k7UsdzTfXZ~r-KNz1x$eH7KYn+ADO?rmiXo?vS_$rdR9TdVQn6SkAs^x! z^#L0gUQ_QZun>}-$ry8DwWDncm|ht@Yxz0DG|U=C5|U!2k>Ae8K6LcDj3Q#f} zdhapRNbvXMr~cm9Tsug~sU)}!D6x=UL{E4RNUpg-(9mUCNueuiTY=SLqdVL8h0!+l zdV^0l$*C*12XCbdJA4{le6>RUvLN4L<_3`-k=3G`|AwTzX8h#AE zx8yY}qf@Rcu-Z3~7C^4jt3A`EgKUnGk#DAo^F=2iVkIF zo?de)8(2$?{Z_T)$8yoM11}TKX1ETU+bnwuEQ=2s&bktSJC-P39L9uIbeqjZJYTBT z-YDgZtesNZj4_?ayW~apy2TB=p79bOP&b;!LeAyCEjmG@nz6Q1_7qqY)Ee%V44y2M zIIi>{wfwOA5l)5hlZzw10K`b?GpD1=ywf81Ja&=ZzVAqsP|pjXL^PIiHRfT@9!N}( zD8i;R&V~Z3*p$d(g9LQ-+pghETBY!*Q9LhHF55JDmSQ#FKVg}~`beQ?-Ridhw2TD} zLBQNZowU99lgG>3?YmI8l8ItnKNe28@&qTAFUplRib$Uh*vTZ>P+%E4@q?}3l(Fti z)QRM_FZd^;(L{Q%C4ks?c7=8#J}7vlO>tHwSIumGbZLn_w`AIh1>arhbjYlB1JEzU z($yntESI^#5PpRSDT+j+P}RfH8uhW_)eT9zK& z69k%cw9tN1#zOBJ7HQO23fC!Wm8AVx6q!Z7h;#)g?Yx`7GBhJQ{xwX93Q2|q@#EPO zcfSd}C4h*)tL?~ji+wKQa1hs3M5k0&d>u0CszTgPpH&v}W6=@UST65|hYf}F=@?hm zRsxISz8YB{Bge>{`lWzN3w>RoEGq1IUocy%F|xmk%`iqsu?t|~yjo3hN>vf7E1sBk zvt?Gx^r%yZiPs13=%5jbGOJ&6Z6&Y}ij6oX&B73oR!-Bju7}#CCEn+?%<1?kM1V@G z))UiM6WngaXNGu%@NeS|vWSr3z;;$?a7xACsU5-pU0a2x%dDimDG^V?30M&5#QLHZ zp6v0stze%Pe@;AU@h6uMSRzI_Y!xwbn6%0P!-9_6M8zosIRF_Qorv99n;8aZ($jr# zhtl%zDh{r?%2}n~DOCh2Dk*j4Y20}9fU>SD(P0O$Q%F2D*5lt>@xcxF77-DtL4Epl z^gaTsrFB|A2l3uG^8@i_>{Ol*LOWG@T#6AKm$L6+p0jviR z8Z%B}@L2|*Q|3je>2V#8C^|{zt5kt1dQeyV}I?yy$PEk3~ zO+~*uJYL+DwTZyOho970lvwQ^P#SYx$5jqN`+<$qBh`slX5dkfs#0wo{NFHmBvNuR zD%a^sbM7nf?cLYG*RV%T6ynXSx4QC7ky+Xn9eQPA=TOyE+`c{@|K6PA__3C#__GM%yXpboJe{2Tq`FFQeM&TB-;{Rdu3I1LI{@ zazHIN3_F2OQ|_#p@aA&$_x%)$-9cbgxv#EZbNUIk<}h#9Ss$SUjw{!5mT%d~Y3RgY z7wbRt2ei28L~4jYkqeF2vI@!EzCc|KuF_zc#nV_5k*OFr8S5a{Ohm!t$g-@+4gxDl zC(B#T&=rE`?YfPY3URAwbX+lhnB2+X1`pH1+`QgI5-;E}@rSdB71iVA6NwTgpVtr_ zGyl4J;4gOW#5>Z?8B34&!bB{M=lM=U&{&j4a5bsx4)kebxNuv6MZ|=WDy%OMMAQ`z z`s5fpjr2_MF|k{V_^Vs~^>%b}J}O>w<-GV(7q6cc%7FZJ(8>17}DUf*ku4MgqyuA6F zKo6b@Aiew#TLT2o!V4hgyezl+2S%J-h8;b-YlRmz)B&fr%&O8@QbUit9}pXTwb`PS z;~y_?aMr4-j{~FFMLnC`0It$d+PzM9Ti_sEzD6LiRFXrX$&`SnB zPIZGz9#mruYXL8_iUG%k1=d7kGC5Vtd5X#A?FREj@bim^E&b}MWfvGvg0({lKfboK zs>-~0mE$ulw zyr_{*GHZ+<3omo@!kX7u_~qZeKX^kpKgzO2Ip=QQud52I#$CQCtpOC%CY~}YeVY}s zX$Z>Cr4_G5(yycHTIg#z%~!F2c8qP9mm%AaeiqV6x}na?!gAH(#8MJL>mjo!m!{Qi;i-v0KYU(c4)a)TsEyO^a7x-sOPZso>v0 zjH7u3B@GyNVo3$C^t4HD&DsOaMgZmUaYJq?Dzw*DCgYqCV6s*%OHndR?pFUrc%IO&TtS1JxzFqPZ$`JaMpwfM29diUZsNXe{z$ygI} z9}Ao{797Zwq_NxgomhGH+Oed-Qo?Q+vI;R^k87?XsM6c))Qzfz{}~ zGGrOBCJ|GziY2(rm>DLvpwNk>mH64=b;)+qd83@rRl!ag)*)E|%DgYQ9fuXW>39~X zO=Sm-?_OM;c9}IX8Ho#6;6~1`Xx+;0O0WQ*28s-q2lpuj;lz7j?PBd;Qag8M? zZe|&6V(M;qI2L2hKdUSM+r%DVRe@F8yZwf1P?IanmEtOdIOb(HWJM2FpjYVUtx8#F z%s`4iO!~&8pw~9nS%}P{uParJ7PLSQXJHFhm@yaxp3w`o5<5G>l{8ir$=}ZEj;aPp zUij_&%<6g}QP(4j2`tvOHSyvFDTlKk1Id5x$Mn>t9#d`3t%WERmfOSI;V1Y|;E|GQ z7apXuaG6C(P%WNaFyYLYNbRJ$lNxJ@7$O==LlafiffOw%IkDoEn&AS6+j7d!7ZX?q zH62s^N?Id{ALL3#rt7m_B5o~8Br&6+(ys!gWF6s^EF#-kcxkz?U@;RSo;`zKC0iA# zWb!OJnLe6ULBq^53%1Pa)M&#xqI#;2G!|6nzBPDG5Qe&PYY8m1zG8({5(G*sC09z1 zB{iPnAgKVNZ%;Rp02M`*_{tqrA{imM1NBW|r{HL@xS zDYmzw(jx_kJryk>*lX&2#o`i{K;eK=3-mo)ywMmkE2pt|$Y#L?rNvuW`r7(;B8NmZsdJ2G6wZw%~$LZzHd;EdB zsVMZb)!OXfWC{H?tSH!ff1P`ul0zxg8cU2URhYDjsghZ08?bivW^AtFC=(_BH8_CR zdU^#`eD|Mo4$pFpntsrW7vgP!RLO(!S20DhuvthBY9?p5wf0PRK3x+t)E3kVg;~&N z>^RbjH)dmW453yeaXi+kgm-%NMAVg2E3kN}!8YpHz?h(0vn7|CdAs^xM4Ez8h*ak6 zuJ8R_%S0Dtxv8KXqRe7vvkCfcgBs>a_A5L8)KFu6QKu#sHDJrND-~EcbWw%8lJ;sN zFo*I?_9~`KtoR#hr!Y%hIa(Lcj=AD{e+`6WB9Q^1$gDtPjcH6`#%jE>x7O{cu^#{4 z{7r?(+2eCHRzRb`lD_-z{s8xR!QalyFHwQEbSsfI)aHa+^xbN`?V;G$3L}ZC?!YpP z9K|WUHjkz8E<8upRb58MSz|r^y*=F9@8{<_h^s57Q(y@K8t5LkRKLYo`Aix}E2l<| zh%jC+g#rdiaxF0Yz%Hv%sP>P>;}9?fzgSdrK?G@G1D@D2)Yu@NTtHwW?;U zFb$sA9vWnV1kel;^q1PBAL;OHNO-8~V zwo>C=qrk%PddQ(2R@Fcv8cVI?Drl=qEfsT9vKE?9M1H>9dB?E#)eXj+SUi$4t9{K% zx~$ZBs{Si|OUTFKp;Tbuslnb>at=vG0}*xGTEx=I>At!iGiL9UEF_U=Eyk}^yxT0+ zT2>dTs%!1B^8+iLC{xMLV-ZC6{LWDBFNQW<535 z$)oZ7XSnPJo1$VeyEs*Q*VK6?V_l&R+lBP6SZce9Jq8AU#uQ1!LrLruzV-K_0UiH_ro7FO2~a#AqBkx zt2T+s|H?IYe|t*StbD91r(02wP01>zg)TAmkE~&_@#!t5zipLQ&oJDd%*r0X(>Dx1 zQ*ONQ#!>CBI*nGn0*eid^f~TQx?D<@#RQ2+rDSPaKDE^+hOL}(Kh&(T)K+XuG+Tmx zN-v^#hfXY_a#Gh9IVaYSb3I4vRssv&UE(SYBE}EeT3_1SfXnLAYD@!;Psti^b_V^b z0k5&vCiS$BV>OYU%I}IQ3)!CK)|7N?;p^k^^7bdQdPW94|Lat4Bgq8Vs-L)L`W`%> zR%}6yRrSL$+`yxF<9`}BE!|oS4vL(mu|A!C#XC#wiHPl{aBSMbKLv%Bunl9@l&(Ga zH!6Rg{{^4-L8-tJBo=B_N~=XEap9NFxyDl~=&d-&vGsKQkyJJ%+4^;Y6R9hURos)CV{RcoIL!x_En0|#15Ih3K) zwRx4K${Gw5jyrAmwF6Ib-?9zIT$e_HRSUhP;zT9lN&uJPsyrJ?t%#Vk>Ic1B>A^v% zGxDk=czjEJ+RrszW6{x2hl``c67&DDosjvUyWf0fyE%sy`_j17%m*y zXIGO}Q)Wr_Nh!a&$h_arEcLgZ{u=JbMTV6u`J1A`7pwc9R0Vum5bw~5!NCJ{0&C=U zJSVeU-je7VODeM`u8hqoohWvzw(Sw40-Y+{d;TqUjjgrDfmuS1>ANVh7VTqyKYSH%rU`&q_oEP81h z*v3wn#tI;A)G4XV3#~y?=^QOABd~fC$SmBU1qX1g&x69%VuGrm@71b0H=gtdF9SW! zHP|e5E}ROhbRBe>gdW7Vi@jZ0QXQ&6T{&$6tLx2N6mq&MHdAj0vLoDLf=URwRbg{gNBO|VeVCo(O7%*)D=K_e47zh1-@zshU^7PlSR*dSdqX|*j2^2 zAv7|!7&2+4mRz@Lu+=H}0XZv$E!n z)Aa<_toDkdgn>gVNXZIbQGWc>%PjHSL!&F8Tbnv)uR# z-1+owpNUyn$nJN#RcH}dl!oAKN5s=;TuNpg9|Dc_n*90g`}KJ7Q&L(bKQ|@{&OrFXrCEW7q5eQb_tf$3RU+SHZPBe3X9Z!#D8u96Nd{I?M0YuGHN z?@DuLU&4viehNXe?0U(aG!}w}iQ5R6K3$BYYHSYMj2(!$vgp0pX_l5^C4r^Ke{c{d zW4T|;NV~6|2fr2rg@!`(q+u6!rlAwZ z4JoXC)q8_`n;WO>-ZOqzF+L`ac8XO_+&Ew_m_I-Em9Y1DReGCS~WXSYDe+X}q z5V=g=B|Xy%F`!mun_+4P`Y zjW+I%#+#Csn1T#P=xF_2*)l4XUtJGZR??59hshqSVx#D6HAvUY6=N_}?kfnxO)}RV zJKkt{*UOs$l)JhOBc^dF(Z5z6&|O)`DE2olXjWHly1-f*bUfUVA0}U$V@!vwt_}wH zz;jepb+edYj$n3XORg)l)lMKC?HA**H7e7-OJB=M*_jndG+(>w5TV+J%Xx9nSv8|; zWeCD{obY~2iD9+hle}w8Fh_0ZHa=MG6lBl|%wSkRpMuDcY`L6?O~JlN2)eKIbY+s6 z9;^Yu@*a0CB)1ySEAae(+D&Edt15=YdIU{{Ug!3!t5R3f1KHyvcP%#)D4)BHrIE@l zS=vgAuB_a*OQ&^7YjmR{^jumY9@{MvA>W z*9-*5;~vIbbY;cqR;%PWOcPja_tp5Zk?ec8EruV)K_b&t`hoo=WnDxEINP2nzq+`z z@-P~CiRs%|3t4F;Rg?y*k}DRvc8wk^B~x2cLb-y=YJRniT6(CfgQ{6$jMM9~_~y;E zb3ZdM6f$_DE~^QoYOc-Blbh1V^Yy;BZ+F~1Ha^pUjYfero!V$%e3&Y$HRjSDu&7PG z*1W@;7@a9&tOv;J(T<1Id}WMuhIf@1eV&_jUEk* ze^}+NFz9rh2gmp=WG`XB-66YTIatOpdhBy9V!clvRql;*@6N4}c%#dMr7iKW)@5}O z>)(cvQ6Z^ZD|~a}v^uWz;5*{^|ITb*bhZy1CFqFLrK1n4Ox%VP{Zp-owxqmvtE}u%_FJl(cfDt_C9R^O)Xu zwO15pVmXsmT=s}Q7}FUr3@^L5JYGJLak|O~RpG(vWJCCx)DjIemPW~(l}yQk!Yii& zmDIll9Z^EV_=U0IasAhhE02*?o^*vXUto=Y5{byznC=2G+L_*WfeA&)*Y}{3=D0G9 zDtb7--HK;xqZJn zx6G4^4MQ7rCsyvrk%~v#)HDpFk(bj|o|yux_Y>Z3-yb~&#$^YsAR7#?or=+4Zc@MiokWjrb%s z%6)BJAMG}tVIxb4B^ZOj@6UDa-MKk;Ib6yhFS{+&{v>ypY*_Vk!94m4n#E>J>_o=Q zLbZkDPCw2$ze;4Q=om9_0R9n2wSgGCMtiuOAn=%qltxEZmErSz1|eaA`W}iB)t}%mp1< zHh7Jg-2J(69*f|R(PN6e_nb;DQx)<83piub`Gd+QdQ(@*!FxwM^-8(9($VbDk{xk2 zcnt>eomIh*;XP((dsDo^HL{TuSW%q&c(+>kz9*+_a*r{KSsJN~)Z>r+EMaelmJMEm zP41Q~UXs@Krl@eMrVTNHMS0(oQ{NdQZ#^@_X&>;b7-~D39a?O*U($>L;13BR-l)61 zDQ|jIB&_ixecq_%-zzQu{HU#f3IaM-{5m*!7_mrHM z*~X-BU>HOg!~V*;Dqw@Sp$%R`mR!UzIKZ6;&fa#o_tlA`?n&;mFjc+~@hn!=eM`%T zuB;n3w0Ym?L=Z9blMj68gO_6Lba?$NHz#Po1eSA_dbI__v{T}d7iTSp)|Ts;p@+zo z^?v*Q`}V1{?(8j}a(23w!N>?K{C!Wkr;jMXQm@wJcaDR%P~=!eC}?A_LyI>IZR8qb zcy7YqGvA*wVb5pS3%9Pi5^Ca~!5ds2nj&|2V619|>!x~dD?Y|0IT2Yx#joP`3G%WhP=WUqy! z2aEW=C&4qq!g&haQm?kb7XC5lSt@J|FqT9AE@k?--U;~-A^m+%8qiiB1EKgd5aro^ zo6%#bpl_CJbmw{xXF4nGuF?^jKI!roSJqp=x(b*i?Hh6ZXEiQKb)Z9On{ zrRdz^DXrX)v2}0^DqfW{EGx^bY6IBYM`IQu`6Z^VjEyNew`vCw*oc0fKet7294b6m z9+CMMPvBseZy1IMl)ZQ$piNyFmnG@kLI%-_PyF5$T>|TXCxeOt%hZ)&s3JKeEd*>B zIbyLDC@u71fp=T}2BT469n@-VTws~HG7O!-v1xaS8U~TfJ4kZxqP7t<0Tnq|NyMMy zVCu>+6cTL24a1<2df--8T{-ksiAE>>H@gBDkC!)*cLmzqZ5YaVyu1~chhdd5jO8#7 zfi6-81`idk^bFbu;mjKM4*uw1h%%hZ)&7>2Pocm&YmgX394U>y)$SrEG` z)i74UmIuQyc48%gZcpP=)2`Lxt4!&nh}4whlG zWAD1qz^Vf406hE3CS}9e2U{Kt!&sfQ1(qv4{KIBv!!QiPFvhc34pxi>0S&`248zdI y@&XHg{GdIY&@c?cFpTjmFR%>5Fbu;mwDEsh2eo}~*Vh670000findShortestPath(); + +// draw the result +drawPaths($max, $positions, $from, $to, $shortestPath, 'image.png'); diff --git a/index.php b/index.php deleted file mode 100644 index 37c9694..0000000 --- a/index.php +++ /dev/null @@ -1,219 +0,0 @@ -setX(rand(0, $max)); - $p->setY(rand(0, $max)); - $positions[] = $p; -} - -// add links -findLink($minDistance, $positions); - -list ($from, $to) = findFromTo($positions); - -$dijkstra = new Dijkstra($positions, $from, $to); -$shortestPath = $dijkstra->findShortestPath(); - -drawPaths($max, $positions, $from, $to, $shortestPath); - -/** - * Add links between the points - * WARNING, this method doesn't work all the time - * - * @param type $minDistance - * @param array $positions - */ -function findLink($minDistance, array &$positions) -{ - for ($i = 0; $i < count($positions); $i++) - { - $p1 = $positions[$i]; - findLinkBetween($minDistance, $p1, $positions); - } -} - -/** - * Add a link between two points if they are at a distance less than $distance. - * This function is recursive with $minDistance *= 2 if no link are found - * - * @param int $minDistance - * @param Point $p1 - * @param array $positions - */ -function findLinkBetween($minDistance, Point &$p1, array &$positions) -{ - for ($o = 0; $o < count($positions); $o++) - { - $p2 = $positions[$o]; - if ($p1->equal($p2)) - { - continue; - } - - $distance = distance($p1, $p2); - if ($distance < $minDistance) - { - $p1->addPoint($p2); - } - } - - if (0 == count($p1->getPoints())) - { - findLinkBetween($minDistance * 2, $p1, $positions); - } -} - -/** - * Find the distance between two points - * @param Point $p1 - * @param Point $p2 - * @return float - */ -function distance(Point $p1, Point $p2) -{ - // distance = square root of ((x2 - x1)^2 + (y2 - y1)^2) - $distance = sqrt(bcpow($p2->getX() - $p1->getX(), 2) + bcpow($p2->getY() - $p1->getY(), 2)); - return $distance; -} - -/** - * Find the "from" and "to" points - * from = most left-upper point - * to = most rigth-lower point - * - * @param array $positions - * @return array - */ -function findFromTo(array $positions) -{ - $from = null; - $to = null; - foreach ($positions as $p) - { - if (is_null($from)) - { - $from = $p; - } - if (is_null($to)) - { - $to = $p; - } - - if ($p->getX() < $from->getX() && $p->getY() < $from->getY()) - { - $from = $p; - } - - if ($p->getX() > $to->getX() && $p->getY() > $to->getY()) - { - $to = $p; - } - } - - return array($from, $to); -} - -/** - * Draw the result - * @param int $max - * @param array $positions - * @param Point $from - * @param Point $to - * @param array $shortestPath - */ -function drawPaths($max, array $positions, Point $from, Point $to, array $shortestPath) -{ - // open background - $image = imagecreatetruecolor($max, $max); - $color = imagecolorallocate($image, 255, 255, 255); - imagefill($image, 0, 0, $color); - - // first run, draw lines - $color = imagecolorallocate($image, 32, 230, 200); - foreach ($positions as $point) - { - foreach ($point->getPoints() as $link) - { - drawLine($image, $point, $link, $color); - } - } - - // then, draw the points - $color = imagecolorallocate($image, 32, 230, 36); - foreach ($positions as $point) - { - imagefilledellipse($image, $point->getX(), $point->getY(), 10, 10, $color); - } - - // draw the shortest path - $color = imagecolorallocate($image, 255, 0, 255); - for ($i = 0; $i < count($shortestPath); $i++) - { - $p = $shortestPath[$i]; - if (isset($shortestPath[$i + 1])) - { - $d = $shortestPath[$i + 1]; - drawLine($image, $p, $d, $color, 3); - } - } - - // and finally, draw the from and to points - $color = imagecolorallocate($image, 255, 0, 255); - imagefilledellipse($image, $from->getX(), $from->getY(), 10, 10, $color); - imagefilledellipse($image, $to->getX(), $to->getY(), 10, 10, $color); - - header("Content-type: image/png"); - imagepng($image); - die(); -} - -/** - * Draw a line - * @param resource $image - * @param Point $p Point 1 - * @param Point $d Point 2 - * @param int $color - * @param int $thick - * @return void - */ -function drawLine($image, Point $p, Point $d, $color, $thick = 1) -{ - if (is_null($p) || is_null($d) || is_null($p->getX()) || is_null($p->getY()) || is_null($d->getX()) || is_null($d->getY())) - { - return; - } - - if ($p->getX() == $d->getX()) - { - $from = $p->getY() < $d->getY() ? $p : $d; - $to = $p->getY() > $d->getY() ? $p : $d; - } - else - { - $from = $p->getX() < $d->getX() ? $p : $d; - $to = $p->getX() > $d->getX() ? $p : $d; - } - - imagesetthickness($image, $thick); - - imageline($image, $from->getX(), $from->getY(), $to->getX(), $to->getY(), $color); -} \ No newline at end of file diff --git a/org/Michotte/Paths/Dijkstra.php b/org/Michotte/Paths/Dijkstra.php deleted file mode 100644 index b62b591..0000000 --- a/org/Michotte/Paths/Dijkstra.php +++ /dev/null @@ -1,241 +0,0 @@ - - * - * Copyright (c) 2012, Benjamin Michotte - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of the University of California, Berkeley nor the - * names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -class Dijkstra -{ - /** - * The positions of nodes - * @var array - */ - private $positions; - - /** - * The starting point - * @var Point - */ - private $from; - - /** - * The ending point - * @var Point - */ - private $to; - - /** - * Array used to save the weights between nodes - * @var array - */ - private $weights; - - /** - * Array used to save the predecessor of nodes - * @var array - */ - private $predecessors; - - /** - * - * @param array $positions - * @param Point $from - * @param Point $to - */ - public function __construct(array $positions, Point $from, Point $to) - { - $this->positions = $positions; - $this->from = $from; - $this->to = $to; - } - - /** - * Find the shortest path between $this->from and $this->to - * @return array - */ - public function findShortestPath() - { - $this->weights = array(); - $this->predecessors = array(); - - foreach ($this->positions as $position) - { - $weight = -1; - $passed = false; - - if ($position->equal($this->from)) - { - $weight = 0; - $passed = true; - } - - // init weights array - $this->weights[$position->getRef()] = array( - 'position' => $position, - 'weight' => $weight, - 'passed' => $passed - ); - - // init predecessors array - $this->predecessors[$position->getRef()] = array( - 'position' => $position, - 'previous' => null - ); - } - - $this->run($this->from); - - return $this->getPath(); - } - - /** - * Find the shortest path between a node and its linked nodes - * @param Point $parent - * @return void - */ - protected function run(Point $parent) - { - // we reached the final point ! - if ($parent->equal($this->to)) - { - return; - } - - // we can set this node has been passed by - $this->weights[$parent->getRef()]['passed'] = true; - - // search for weight between $parent and its children - foreach ($parent->getPoints() as $child) - { - $this->calculateWeight($parent, $child); - } - - // search for the next parent (smallest weight) - // we have to find the smallest weight for a node we didn't passed by atm - $smallest = INF; - $nextParent = null; - foreach ($this->weights as $weight) - { - if ($weight['weight'] < $smallest && $weight['weight'] != -1 && ! $weight['passed']) - { - $smallest = $weight['weight']; - $nextParent = $weight['position']; - } - } - if (!is_null($nextParent)) - { - $this->run($nextParent); - } - } - - /** - * Return the path (array of Point) between $this->from and $this->to. - *
Warning, the result can be incomplete (no path found) - * @return array - */ - protected function getPath() - { - $path = array($this->to); - - $point = $this->to; - while (true) - { - foreach ($this->predecessors as $predecessor) - { - if ($predecessor['position']->equal($point)) - { - $point = $predecessor['previous']; - $path[] = $point; - - break; - } - } - - // $point is null -> path impossible - if (is_null($point)) - { - unset($path[count($path) - 1]); - break; - } - - if ($point->equal($this->from)) - { - break; - } - } - - $path = array_reverse($path); - return $path; - } - - /** - * Search for the weight of a path between two nodes. - * @link http://en.wikipedia.org/wiki/Dijkstra%27s_algorithm For more info about the algorithm - * @param Point $parent - * @param Point $child - * @return void - */ - protected function calculateWeight(Point $parent, Point $child) - { - /* - * Dijkstra algo says : - * - * IF (child-node is not traversed yet) AND - * (WEIGHT(parent-node) + WEIGHT(DISTANCE(parent-node, child-node) < WEIGHT(child-node) OR WEIGHT(child-node) = -1) - * THEN - * WEIGHT(child-node) = WEIGHT(parent-node) + WEIGHT(DISTANCE(parent-node, child-node) - * PREDECESSOR(child-node) = parent-node - * ENDIF - */ - if (! $this->weights[$child->getRef()]['passed'] - && ($this->weights[$parent->getRef()]['weight'] + $this->distance($parent, $child) < $this->weights[$child->getRef()]['weight'] - || $this->weights[$child->getRef()]['weight'] == -1)) - { - $this->weights[$child->getRef()]['weight'] = $this->weights[$parent->getRef()]['weight'] + $this->distance($parent, $child); - $this->predecessors[$child->getRef()]['previous'] = $parent; - } - } - - /** - * Find the distance between 2 points - * @var Point $p1 - * @var Point $p2 - * @return double - */ - protected function distance(Point $p1, Point $p2) - { - // distance = square root of ((x2 - x1)^2 + (y2 - y1)^2) - $distance = sqrt(bcpow($p2->getX() - $p1->getX(), 2) + bcpow($p2->getY() - $p1->getY(), 2)); - return $distance; - } -} - diff --git a/org/Michotte/Paths/Point.php b/org/Michotte/Paths/Point.php deleted file mode 100644 index d4072ac..0000000 --- a/org/Michotte/Paths/Point.php +++ /dev/null @@ -1,155 +0,0 @@ - - * - * Copyright (c) 2012, Benjamin Michotte - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of the University of California, Berkeley nor the - * names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -class Point -{ - /** - * @var float $x The X coordinate of the Point - */ - private $x; - - /** - * @var float $y The y coordinate of the Point - */ - private $y; - - /** - * @var Point[] $points The "Point"s linked to this Point - */ - private $points; - - public function __construct() - { - $this->points = array(); - } - - /** - * Set the X coordinate - * - * @param float $x - * @return Point - */ - public function setX($x) - { - $this->x = $x; - return $this; - } - - /** - * Get the X coordinate - * - * @return float - */ - public function getX() - { - return $this->x; - } - - /** - * Set the Y coordinate - * - * @param float $y - * @return Point - */ - public function setY($y) - { - $this->y = $y; - return $this; - } - - /** - * Get the Y coordinate - * - * @return float - */ - public function getY() - { - return $this->y; - } - - /** - * Add link - * - * @param Point $point - * @return Point - */ - public function addPoint(Point $point) - { - if (!in_array($point, $this->points)) - { - $this->points[] = $point; - } - - // add the reverse point - if (!in_array($this, $point->points)) - { - $point->points[] = $this; - } - return $this; - } - - /** - * Get links - * - * @return array - */ - public function getPoints() - { - return $this->points; - } - - /** - * Identify the Point by the concatenation of x and y - * @return string - */ - public function getRef() - { - return $this->getX() . '-' . $this->getY(); - } - - /** - * Simple equal function - * @param Point $point - * @return boolean - */ - public function equal($point) - { - if (! ($point instanceof Point)) - { - return false; - } - return $this->getRef() == $point->getRef(); - } -} \ No newline at end of file diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..7db290f --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,16 @@ + + + + + + tests + + + + + + src/ + + + + diff --git a/src/Dijkstra.php b/src/Dijkstra.php new file mode 100644 index 0000000..6c68e3e --- /dev/null +++ b/src/Dijkstra.php @@ -0,0 +1,139 @@ +positions = $positions; + $this->from = $from; + $this->to = $to; + } + + public function findShortestPath(): array + { + $this->weights = []; + $this->predecessors = []; + + foreach ($this->positions as $position) { + $weight = -1; + $passed = false; + + if ($position->equals($this->from)) { + $weight = 0; + $passed = true; + } + + // init weights array + $this->weights[$position->ref] = [ + 'position' => $position, + 'weight' => $weight, + 'passed' => $passed, + ]; + + // init predecessors array + $this->predecessors[$position->ref] = [ + 'position' => $position, + 'previous' => null, + ]; + } + + return $this->run($this->from)->getPath(); + } + + protected function run(Point $parent): Dijkstra + { + // we reached the final point ! + if ($parent->equals($this->to)) { + return $this; + } + + // we can set this node has been passed by + $this->weights[$parent->ref]['passed'] = true; + + // search for weight between $parent and its children + foreach ($parent->points as $child) { + $this->calculateWeight($parent, $child); + } + + // search for the next parent (smallest weight) + // we have to find the smallest weight for a node we didn't passed by atm + $smallest = INF; + $nextParent = null; + foreach ($this->weights as $weight) { + if ($weight['weight'] < $smallest && $weight['weight'] !== -1 && ! $weight['passed']) { + $smallest = $weight['weight']; + $nextParent = $weight['position']; + } + } + if (! is_null($nextParent)) { + return $this->run($nextParent); + } + + return $this; + } + + protected function getPath(): array + { + $path = [$this->to]; + + $point = $this->to; + while (true) { + foreach ($this->predecessors as $predecessor) { + if ($predecessor['position']->equals($point)) { + $point = $predecessor['previous']; + $path[] = $point; + + break; + } + } + + // $point is null -> path impossible + if (is_null($point)) { + unset($path[count($path) - 1]); + break; + } + + if ($point->equals($this->from)) { + break; + } + } + + $path = array_reverse($path); + + return $path; + } + + protected function calculateWeight(Point $parent, Point $child): void + { + /* + * Dijkstra algo says : + * + * IF (child-node is not traversed yet) AND + * (WEIGHT(parent-node) + WEIGHT(DISTANCE(parent-node, child-node) < WEIGHT(child-node) OR WEIGHT(child-node) = -1) + * THEN + * WEIGHT(child-node) = WEIGHT(parent-node) + WEIGHT(DISTANCE(parent-node, child-node) + * PREDECESSOR(child-node) = parent-node + * ENDIF + */ + if (! $this->weights[$child->ref]['passed'] + && ($this->weights[$parent->ref]['weight'] + self::distance($parent, $child) < $this->weights[$child->ref]['weight'] + || $this->weights[$child->ref]['weight'] === -1)) { + $this->weights[$child->ref]['weight'] = $this->weights[$parent->ref]['weight'] + self::distance($parent, $child); + $this->predecessors[$child->ref]['previous'] = $parent; + } + } + + public static function distance(Point $p1, Point $p2): float + { + // distance = square root of ((x2 - x1)^2 + (y2 - y1)^2) + return sqrt(bcpow($p2->x - $p1->x, 2) + bcpow($p2->y - $p1->y, 2)); + } +} diff --git a/src/Point.php b/src/Point.php new file mode 100644 index 0000000..f3ae2e1 --- /dev/null +++ b/src/Point.php @@ -0,0 +1,38 @@ +x = $x; + $this->y = $y; + $this->points = []; + $this->ref = "{$x}-{$y}"; + } + + public function addPoint(Point $point): Point + { + if (! in_array($point, $this->points)) { + $this->points[] = $point; + } + + // add the reverse point + if (! in_array($this, $point->points)) { + $point->points[] = $this; + } + + return $this; + } + + public function equals(Point $point): bool + { + return $this->ref === $point->ref; + } +} diff --git a/tests/DijkstraTest.php b/tests/DijkstraTest.php new file mode 100644 index 0000000..af7c2d9 --- /dev/null +++ b/tests/DijkstraTest.php @@ -0,0 +1,63 @@ +addPoint($p2); + + $dijkstra = new Dijkstra([$p1, $p2], $p1, $p2); + $path = $dijkstra->findShortestPath(); + + $this->assertCount(2, $path); + $this->assertTrue($path[0]->equals($p1)); + $this->assertTrue($path[1]->equals($p2)); + } + + /** @test */ + public function it_should_find_a_short_path() + { + $p1 = new Point(1, 1); + $p2 = new Point(2, 2); + $p3 = new Point(3, 8); + $p4 = new Point(8, 2); + $p5 = new Point(10, 10); + + $p1->addPoint($p2); + + $p2->addPoint($p3); + $p2->addPoint($p4); + + $p3->addPoint($p4); + $p4->addPoint($p5); + + $dijkstra = new Dijkstra([$p1, $p2, $p3, $p4, $p5], $p1, $p5); + $path = $dijkstra->findShortestPath(); + + $this->assertCount(4, $path); + $this->assertTrue($path[0]->equals($p1)); + $this->assertTrue($path[1]->equals($p2)); + $this->assertTrue($path[2]->equals($p4)); + $this->assertTrue($path[3]->equals($p5)); + } + + /** @test */ + public function it_could_have_an_impossible_path() + { + $p1 = new Point(1, 1); + $p2 = new Point(2, 2); + + $dijkstra = new Dijkstra([$p1, $p2], $p1, $p2); + $path = $dijkstra->findShortestPath(); + $this->assertCount(1, $path); + } +} diff --git a/tests/PointTest.php b/tests/PointTest.php new file mode 100644 index 0000000..eab2aa5 --- /dev/null +++ b/tests/PointTest.php @@ -0,0 +1,36 @@ +assertEquals('10-12', $point->ref); + } + + /** @test */ + public function it_should_add_a_point() + { + $point = new Point(10, 12); + $point2 = new Point(18, 12); + + $point->addPoint($point2); + + $this->assertTrue(in_array($point2, $point->points)); + } + + /** @test */ + public function it_should_not_equals() + { + $point = new Point(10, 12); + $point2 = new Point(18, 12); + + $this->assertFalse($point->equals($point2)); + } +} From 12c5b15665b1e949ef0f1ad9f416fcfc5d6754c1 Mon Sep 17 00:00:00 2001 From: Benjamin Michotte Date: Thu, 28 Feb 2019 09:07:01 +0100 Subject: [PATCH 2/4] .scrutinizer.yml --- .scrutinizer.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .scrutinizer.yml diff --git a/.scrutinizer.yml b/.scrutinizer.yml new file mode 100644 index 0000000..c626dc0 --- /dev/null +++ b/.scrutinizer.yml @@ -0,0 +1,18 @@ +filter: + excluded_paths: [tests/*] + +checks: + php: + remove_extra_empty_lines: true + remove_php_closing_tag: true + remove_trailing_whitespace: true + fix_use_statements: + remove_unused: true + preserve_multiple: false + preserve_blanklines: true + order_alphabetically: true + fix_php_opening_tag: true + fix_linefeed: true + fix_line_ending: true + fix_identation_4spaces: true + fix_doc_comments: true From 32c719b229a3aa28e928fb33879c461d4181d53a Mon Sep 17 00:00:00 2001 From: Benjamin Michotte Date: Thu, 28 Feb 2019 09:12:58 +0100 Subject: [PATCH 3/4] add dev branch --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index c25e9b0..89811c7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,3 +18,4 @@ script: branches: only: - master + - develop From b1d8b6a7467ebfa7d65e023bc2eec11ce6d2a2d9 Mon Sep 17 00:00:00 2001 From: Benjamin Michotte Date: Thu, 28 Feb 2019 09:20:49 +0100 Subject: [PATCH 4/4] fix scrutinizer build --- .editorconfig | 2 +- example/helpers.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.editorconfig b/.editorconfig index 8071135..4180091 100644 --- a/.editorconfig +++ b/.editorconfig @@ -7,7 +7,7 @@ trim_trailing_whitespace = true insert_final_newline = true indent_brace_style = 1TBS indent_style = space -indent_size = 2 +indent_size = 4 [**.php] indent_size = 4 diff --git a/example/helpers.php b/example/helpers.php index 747f4e2..6a77887 100644 --- a/example/helpers.php +++ b/example/helpers.php @@ -83,7 +83,7 @@ function drawPaths(int $max, array $positions, Point $from, Point $to, array $sh imagefilledellipse($image, $from->x, $from->y, 10, 10, $color); imagefilledellipse($image, $to->x, $to->y, 10, 10, $color); - $content = imagepng($image, $filename); + imagepng($image, $filename); } function drawLine($image, Point $point1, Point $point2, $color, int $thick = 1): void