Compare commits
1150 Commits
Lukovnikov
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
| 2735239aca | |||
| 186cd9f688 | |||
| 210ea8ece8 | |||
| a4d9a226c1 | |||
| bcdaf65dbc | |||
| cec177e26d | |||
| 2d36dd7ef3 | |||
| ad704a27ab | |||
| 3b1c21a8b1 | |||
| 7f540020bc | |||
| 52fbd9c2b1 | |||
| 8deead6d69 | |||
| c5eef27dc9 | |||
| 50e38b4d79 | |||
| 87269b78c5 | |||
| 714f25fc82 | |||
| 78eaf79bc4 | |||
| 31190f84f5 | |||
| d025768363 | |||
| 34eade7e8d | |||
| a5777ae4bc | |||
| 0b1738c0ff | |||
| f5cb490725 | |||
| 991b843cec | |||
| 7fcff47515 | |||
| e718740494 | |||
| a43956a823 | |||
| 7e1a9c1dba | |||
| db85133d0c | |||
| 178a3ac278 | |||
| f105cea289 | |||
| 53c024e79d | |||
| 4b56346d15 | |||
| fca5e74d90 | |||
| d81df337aa | |||
| c47ac035f6 | |||
| 1b75244031 | |||
| 0b409ad19b | |||
| c42887cdd4 | |||
| c6140866fc | |||
| 4502a81d29 | |||
| 1f016b85f0 | |||
| 4058e3b2f4 | |||
| 0423b103cc | |||
| 41b913b317 | |||
| 0df82365b5 | |||
| 93fb15f301 | |||
| 33a5503164 | |||
| f6d82124fc | |||
| 979a03c36f | |||
| 9b55830b58 | |||
| 5c7b6b0ac1 | |||
| 0310e07589 | |||
| 98d32c7cf1 | |||
| befc24663a | |||
| 372cbe7db4 | |||
| 3a5e332d89 | |||
| 2fe4c1bb75 | |||
| b41bf0e9f3 | |||
| b54e87ad1c | |||
| 590204844a | |||
| fabb5cd5c1 | |||
| d9a501b086 | |||
| b4073c69a4 | |||
| 42999d4181 | |||
| 6bf6bc0c5f | |||
| 5e3f0b10e7 | |||
| f36e519a20 | |||
| 1dedd07bc3 | |||
| aeb797e271 | |||
| f713dc3e6a | |||
| 4f25ffe3b5 | |||
| e47b0a93c7 | |||
| 78ec64cb91 | |||
| 14c0a27319 | |||
| ec1d4f155d | |||
| c4be844d2c | |||
| 81964eb444 | |||
| 0539c6b573 | |||
| 0e945052fc | |||
| 98f567dc82 | |||
| 27f51cfb57 | |||
| 0a0e3edd39 | |||
| f2aa20ced7 | |||
| f40cef0d15 | |||
| b7274bb9db | |||
| f907f973d9 | |||
| b55718f0c1 | |||
| 0b482ef4e5 | |||
| 00ff5134fc | |||
| 414db1c72f | |||
| a0b58a4489 | |||
| 0ea739636a | |||
| d801ac985c | |||
| 7e5d4cad71 | |||
| a8c94734c0 | |||
| 4efa5dcb85 | |||
| 05f56d5be3 | |||
| e87bdd2808 | |||
| b6fd0051f9 | |||
| 77c6985b53 | |||
| 75f807b85b | |||
| 8e0b2b71a2 | |||
| c2a40c8ce1 | |||
| 44919e72ee | |||
| cebf01f0d8 | |||
| 8c12341e03 | |||
| 0bda7aa621 | |||
| 6068b06062 | |||
| b4154dafa7 | |||
| d424eb9a23 | |||
| a481877040 | |||
| 828eac11fa | |||
| be1af7bf6c | |||
| 3b67462c48 | |||
| 452247bf09 | |||
| e3c7c29712 | |||
| 46bb2fde1e | |||
| b1060f2f73 | |||
| 89016b59a9 | |||
| 64fd540b45 | |||
| af055d4e17 | |||
| 8c2b455ca5 | |||
| 49fa517565 | |||
| 19f9b5403c | |||
|
|
3428678d8a | ||
| bbd059e8b2 | |||
| 2360b42a88 | |||
| fa4ec42cd2 | |||
| 3f683b674e | |||
| d548b612e6 | |||
| 73107e0565 | |||
| 423acea089 | |||
| 4a8728f3e0 | |||
| 951c90db20 | |||
| cefff5de8d | |||
| 8ebd282344 | |||
| 627e58a70e | |||
| 642874f0c2 | |||
| 18359cc7ad | |||
| b1b22678a2 | |||
| 0354fd9c79 | |||
| 5a43e1610b | |||
| 1350cb60b3 | |||
| e8e43b0919 | |||
| 287a6098b5 | |||
| dbd088daff | |||
| b091b98a93 | |||
| 9ae55f1244 | |||
| 80d51b3f93 | |||
| 451ae4ce4b | |||
| da8127d37a | |||
| 20982d4172 | |||
| 6db272487e | |||
| e14abb6194 | |||
| 9a8a82a978 | |||
|
|
81ffeb0def | ||
| 6d17293486 | |||
| e9ede2ecb3 | |||
| 153f2a092c | |||
| 057dfb01af | |||
| da3cf034d1 | |||
|
|
41004bcb8a | ||
|
|
83e323fff6 | ||
|
|
0822d2ac71 | ||
|
|
a88252fe56 | ||
|
|
13315fd2c9 | ||
| ce521a257f | |||
|
|
cda95828a6 | ||
|
|
f4666dae8b | ||
|
|
33c3f109fd | ||
|
|
1d4bd0bf73 | ||
|
|
e3bcc19952 | ||
| df3afe9dbf | |||
| 886278c62f | |||
| 5b0eb0874a | |||
| dd698bad55 | |||
| 031b48653f | |||
| f3908409ce | |||
| 245a1e7c4b | |||
| 10dcc4307f | |||
| 9c6804eea8 | |||
| 55ff0515e9 | |||
| 52cba853dd | |||
| 7cbd75ee34 | |||
|
|
b1a4e4185e | ||
|
|
3d9390a894 | ||
| f7ce8e1682 | |||
| 275f6b7297 | |||
| 12508587d6 | |||
| f64679ead2 | |||
| 8460bf6405 | |||
| 59410050ca | |||
| f16b87c034 | |||
| 8ce584051b | |||
| e6e4ae9a99 | |||
| 931e6a9ccf | |||
| e11bfb0f7a | |||
| 38f361bdc4 | |||
| 434b63d633 | |||
| 9f8e9e1384 | |||
| cb04e571d3 | |||
| edbd5e15b8 | |||
| 23556d3900 | |||
| e080be3fc1 | |||
| e0f27a4ef0 | |||
| 72d33c2b84 | |||
| 16cd930cdd | |||
| 5f5b14b739 | |||
| 3035b7e15b | |||
| 8ba20c54b9 | |||
| cb5e43f857 | |||
| 641e1a8aac | |||
| dfd34a3974 | |||
| 539dba32f9 | |||
| 4e1fab741f | |||
| d5d4c702b5 | |||
| 3bef169aba | |||
| 1eb2b4da53 | |||
| a5c368e145 | |||
| 7aca816d85 | |||
| 979fcfa452 | |||
| 7e0a54a817 | |||
| 4f7e692ab3 | |||
| 5abb175441 | |||
| 068a5f0590 | |||
| 7e53aebb10 | |||
| 564db4d839 | |||
| 78513d3506 | |||
| 80b33d9f61 | |||
| 80381f6b5d | |||
| ff14e2635a | |||
| 54418fac12 | |||
| 60c6452b4a | |||
| c628f43dbe | |||
| 7e723606cb | |||
| feb001df7c | |||
| a9deaa51ec | |||
| ed33210af4 | |||
| 9dcaef45ec | |||
| 482048fc8a | |||
| 0ce55669c3 | |||
| 51f77e7a5a | |||
| 86dfd72c42 | |||
| 24ec288ee8 | |||
| b223ce3e2e | |||
| dff3e7f934 | |||
| d8972df779 | |||
| 07e5f05661 | |||
| a9ee9e60f2 | |||
| 13a81dbc33 | |||
| 1440f8a4b6 | |||
| 2f896f1f87 | |||
| 326c84c3f0 | |||
| 04a49325d3 | |||
| e475853175 | |||
| 52a4b077fa | |||
| 6a0209fb6b | |||
| 1949ccff77 | |||
| e5dd2683e9 | |||
| 2769e3eb01 | |||
| 8f6c31904c | |||
| cb4a60d325 | |||
| 541d73abd2 | |||
| d5b077f8dd | |||
| 61f50cedde | |||
| 6e7f88a63c | |||
| 315b554b85 | |||
| 0415405f0d | |||
| befe3ecf93 | |||
| 1ee21603e5 | |||
| bf2dc50479 | |||
|
|
feaa05f32d | ||
| 77485a5318 | |||
| fb36061d9a | |||
| 4f8b4e8a49 | |||
| 9ff0e6aa20 | |||
| c7e471c87a | |||
| 72d2269634 | |||
| d25d91e54d | |||
| 58ca1450e3 | |||
| d47b06ae6f | |||
| 74c13ec0fb | |||
| 319171fe2c | |||
| 0c2e487472 | |||
| a703898619 | |||
| e464e4e1e0 | |||
| e741fab112 | |||
| 05980b5e12 | |||
| 350ccb861e | |||
| 766b3facf2 | |||
| bebedf835e | |||
| ee9aedf359 | |||
| b9a3497b5a | |||
| 86190175eb | |||
| 07dc80bee2 | |||
| 29a062508f | |||
| f48e920dd1 | |||
| 536c36b9fb | |||
| 35ab0b7347 | |||
| e435c7fcc1 | |||
| c586553b8b | |||
| 61908239da | |||
| 41433c287a | |||
| 43ea04de23 | |||
| a95db6edf3 | |||
| 7e124d2f50 | |||
| 3cba71c6e7 | |||
| 4382efbe09 | |||
| f1b42d1c24 | |||
| 66a16fb3a1 | |||
| f776455086 | |||
| 71b3fecbcf | |||
| 9e04d5930e | |||
| 7cb73be269 | |||
| 476ca1451e | |||
| e053d90c29 | |||
| fab92c6734 | |||
| 3dc7e19f49 | |||
| 43ca559493 | |||
| d3e6a241b2 | |||
| 8f8ae504e3 | |||
| 9cd3c9caf5 | |||
| 97dbb34c10 | |||
| b6fd8ae06f | |||
| e0b142c994 | |||
| e3f4f4c57f | |||
| 79dc0854a1 | |||
| a32ff24a11 | |||
| 797c260aaa | |||
| 3d7c072106 | |||
| 9800ba7078 | |||
| 1b31955e9c | |||
| e6c58ac054 | |||
|
|
653af1f2f6 | ||
|
|
f26da8a6cc | ||
|
|
3c644d29b0 | ||
|
|
efd614bd23 | ||
| 443ec394f0 | |||
| 9ef6acffb0 | |||
| 768c8e64f2 | |||
| 8353478190 | |||
| 20a235975f | |||
| 5597f8a8a7 | |||
| bde87a177b | |||
| b7d5bf54dd | |||
| 516ac2e1fb | |||
| df10b20f41 | |||
| 380b683b37 | |||
| f7b3ae0878 | |||
| 7371fb231a | |||
| 7e01d972c1 | |||
| 8963a00e6e | |||
| 089cd9ac58 | |||
| e27b2772db | |||
| c967240036 | |||
| dd03fdd059 | |||
| aadb5b2e3b | |||
| 118945f625 | |||
| c708d708e3 | |||
| 7eb2e40099 | |||
| 8aa0d0ee0e | |||
| 49ef151e63 | |||
| 9f5a0055f3 | |||
| 4c14b6d0b3 | |||
| d8943615fa | |||
| 6491d3a79e | |||
| 4633efddae | |||
| f4a032747b | |||
| cd5b180383 | |||
| d576fe4a6b | |||
| 02842819e9 | |||
| 9395b9807c | |||
| 9a6cf10396 | |||
|
|
9849075a38 | ||
| 82cb313306 | |||
| 31270e0ffd | |||
| 975ffb8a54 | |||
| bbd26d8f8d | |||
| da1a8356e7 | |||
| 07523b3617 | |||
| 0e4684b897 | |||
| 2a4a8114a7 | |||
| 7aca3961e3 | |||
| aae08b7336 | |||
| 11d65436e1 | |||
| fc6b64b7cd | |||
| 1dcc56d0bc | |||
| 3032b5283b | |||
| 68d1dfa68e | |||
| 07e7eaa7ee | |||
| 163eb1010f | |||
| 1a9317dad7 | |||
| d4aa0588c3 | |||
| 3773442642 | |||
| af06031de7 | |||
| 22a395ad0a | |||
|
|
27c2f99467 | ||
| bbdb44dd88 | |||
|
|
86b31473f3 | ||
| 82943137b3 | |||
| bca3eb1b7c | |||
| b29a0309a1 | |||
| 2e68f1a389 | |||
| 59a488fec2 | |||
| 4bc13373b1 | |||
| d3e3962dd8 | |||
|
|
f393ad5203 | ||
| 4ae10433ff | |||
| 7f4c49e5fc | |||
| 04657980e7 | |||
| 37528912c7 | |||
| 7164968879 | |||
| 95b152b17c | |||
|
|
5378824649 | ||
| 47566646d9 | |||
|
|
f7577f803c | ||
| 5b3441628a | |||
| 89e9ab282a | |||
| 08255c2e97 | |||
| a966076c00 | |||
| acbd3a3892 | |||
| 1b07ec908d | |||
| 735626045b | |||
| 4139af5eae | |||
| 56ff7f317a | |||
| 1921a993c9 | |||
| d3f918f9be | |||
| 31726394a9 | |||
| e677d750f7 | |||
| 52a5aa8fc6 | |||
| 859ee847dd | |||
| a740a6cb6b | |||
| 9d935dc1f9 | |||
| 132e7e049b | |||
| 706e7b5e7f | |||
| 926c13b163 | |||
| a9270288ba | |||
| 1c5c49cd0d | |||
| d19b1788cf | |||
| a358963ffc | |||
| 901031fd0a | |||
| 58ab8d9227 | |||
| 6bed72c0f5 | |||
| 95f6d90af3 | |||
| b908d49a10 | |||
| 588e654462 | |||
| c254158275 | |||
| 4144c3d390 | |||
| 22acd557d1 | |||
| 579df4fff2 | |||
| 39db199b6e | |||
| 05c3a5cb6c | |||
| f79bddbef7 | |||
| 6a4e82001c | |||
| fa78ae9472 | |||
| ddcf9d2c34 | |||
| 4ce7536bbf | |||
| d8ae32af35 | |||
| d59cd16706 | |||
| 77c787c2f4 | |||
|
|
983ef65bef | ||
|
|
95805467ab | ||
| 29f33fa407 | |||
| 4427e6d0a1 | |||
| 0c93c3a3b0 | |||
| 70f49bb8c5 | |||
| 1c1d20e017 | |||
| 140a03b1a1 | |||
| 4db9491cec | |||
| a50bd41ccf | |||
| c3fb6c4bd9 | |||
| 396b19bf71 | |||
| 268d492389 | |||
| 7e0ac65b64 | |||
| 9c066a3e03 | |||
| 23012e3e84 | |||
| c5baf3661a | |||
| 04e7b32684 | |||
| 86f4936e0e | |||
| 63e6158583 | |||
| 03eb347e0d | |||
| 759c0eab91 | |||
| e8418bd446 | |||
| 5f7e7cec65 | |||
| e1d3f90053 | |||
| 6fc7a657d2 | |||
| 07effa49c2 | |||
| 834bc5e7b0 | |||
| 610bef7926 | |||
| 5343261de3 | |||
| 238245c512 | |||
|
|
535c706af8 | ||
| 7fafaba9eb | |||
| f589771843 | |||
| 673c18cfaf | |||
| 744a44c5bc | |||
| 054035deac | |||
| c30410f5b0 | |||
| 1aacdc4e26 | |||
| 60b42ee51c | |||
| bb11731efd | |||
| afa3c146b0 | |||
| 21985795a1 | |||
|
|
65594bdb6e | ||
|
|
5b68540615 | ||
|
|
c19eb1a1f6 | ||
|
|
74928a997a | ||
| 31f166f293 | |||
| 7782d2e812 | |||
| 59b6e40e4a | |||
| 762517d624 | |||
| e63c31a3e3 | |||
| 056c905032 | |||
| bd2c4fd939 | |||
| 5dc9777c5a | |||
| 29495a0eda | |||
| 52896e6f1d | |||
| d04a7c0c47 | |||
| 69095c1b52 | |||
| 8116c3836d | |||
|
|
de1170df68 | ||
| b652b1515c | |||
|
|
7a84310d5d | ||
| a8baea68c7 | |||
| 27417decc0 | |||
| da179c5dde | |||
| 951190aa68 | |||
| 062a96a0b4 | |||
| ef877978d2 | |||
| e54b6c0a7e | |||
| 07fe035d63 | |||
| 1224a5afee | |||
|
|
fa3d95b5c0 | ||
|
|
f3270c4197 | ||
|
|
a339b8fb6f | ||
| 156aa5d75d | |||
| 1edbb037d0 | |||
|
|
308ee2d716 | ||
|
|
a912487cdc | ||
|
|
c1086c89c3 | ||
| d452965a3b | |||
| 940d38fba5 | |||
| 75fcff26d9 | |||
| 181ce4b3f1 | |||
| 241f2a0211 | |||
| d509d14862 | |||
| b335daa881 | |||
| 1bb67bff28 | |||
| f66f706883 | |||
| 98874a9f79 | |||
| 854074ed0d | |||
| 729a0a8695 | |||
| cc8f679e79 | |||
| a3c64db4af | |||
| b6debe4706 | |||
| c6f4d5d158 | |||
| 665534bf01 | |||
| c59ca74ab5 | |||
| 537bbb1458 | |||
| c25ff8ca6e | |||
| 16c614341e | |||
| 4d0d97116e | |||
| 82e3cba902 | |||
| 152123768c | |||
| a1f157b283 | |||
| 00453eb033 | |||
| c009c610a6 | |||
| d2001eaf53 | |||
| eb6587c537 | |||
| a3e40fe0d5 | |||
| 9be996f8cd | |||
|
|
56a26a676a | ||
| e611cfe31b | |||
| 9f9158b0c4 | |||
| 58b2b73c8a | |||
|
|
949252d245 | ||
| 24f11880e1 | |||
| 5aac691724 | |||
| 70c8bbf3e7 | |||
| 0f03b4edd4 | |||
| 313fe75c36 | |||
| 2f980aaa77 | |||
| ae812457fb | |||
| 75a182304e | |||
| 5959566f59 | |||
| 6aca3619ba | |||
| 5827416a2b | |||
| 2ff608b88f | |||
| 666bc0af37 | |||
| 5a978707d8 | |||
| d8fb7ab226 | |||
|
|
c096613e08 | ||
| 06f8192f6a | |||
| c8694aa053 | |||
| 5fce1bb8a6 | |||
| 1d5a0f1539 | |||
| 32ed692fd8 | |||
| 26424c7d61 | |||
| 483744b075 | |||
| 473003b4ce | |||
| 498d1250b9 | |||
| de1f262581 | |||
| d3212347dc | |||
| 1db901aba1 | |||
| 55a9536ff9 | |||
| 6adc15ccd3 | |||
| 1b7ad33278 | |||
| f1dfbea54b | |||
| a169773089 | |||
| 6b26bf2b0c | |||
| 852a8d43bd | |||
| dde7c1fce5 | |||
| 52dd19b535 | |||
| 7ea5690be9 | |||
| 1923a02552 | |||
| 3672b4f4d0 | |||
| 3daef7dcc9 | |||
| e12d37f94b | |||
| ccf6b0e4dc | |||
| 86b738f6a4 | |||
| 6005f2f8b2 | |||
| 28c4c61a68 | |||
| b2128b0010 | |||
| b8b7971d86 | |||
| 535fd115b5 | |||
| f57786fe3f | |||
| 09456b2a41 | |||
| 058a921ad5 | |||
| 45df39223d | |||
| c915f50377 | |||
| a95dbdf35b | |||
| 9d77a348f7 | |||
| c19fa42056 | |||
| 3b072cee1c | |||
| 2163ccd0aa | |||
| 4e29dfb4bb | |||
| f89f54eddc | |||
| e8de0e29c4 | |||
| 06a8a01c7f | |||
| 098ccd7af1 | |||
| 047c2baf6d | |||
| 303bcf3eae | |||
| 8f4cf09083 | |||
| 0f5089fb24 | |||
| d975a736d1 | |||
| 82e023dd36 | |||
| ad8ae841b1 | |||
| 324333c9aa | |||
| e5493a5439 | |||
|
|
107d5cbd61 | ||
|
|
eeecdd61cd | ||
|
|
d8ab579a46 | ||
|
|
5207bb8682 | ||
|
|
0d72312f50 | ||
|
|
d677c5c794 | ||
|
|
e3e6e176a4 | ||
|
|
3334a1de97 | ||
|
|
aaa3d93224 | ||
|
|
99daffa959 | ||
|
|
af4dae63f5 | ||
|
|
d8eebad0a5 | ||
|
|
ccb1fca2a9 | ||
|
|
5647ac84f7 | ||
|
|
055a73325b | ||
|
|
d6286add4a | ||
|
|
615c256eb0 | ||
|
|
3d10d8f6b8 | ||
|
|
8ea4281568 | ||
|
|
012638e1d8 | ||
| e71fe47f8c | |||
|
|
af4d1c77c8 | ||
|
|
b26bb1d3e7 | ||
| 008b62dac7 | |||
| fd25842806 | |||
| 792142a260 | |||
| 928ac824f2 | |||
|
|
5525af0b26 | ||
|
|
98c17e18fa | ||
|
|
05a9bdd8f4 | ||
| c9448652cf | |||
|
|
d05d292101 | ||
|
|
e45f8cba64 | ||
| 8dafdab90e | |||
|
|
3fa79f06c3 | ||
| 8a6344f301 | |||
| 4422dcb9e7 | |||
|
|
e10b075b06 | ||
| 8331065da2 | |||
| f89d10b615 | |||
| 47404cfa9d | |||
| 74935d19f7 | |||
| 91f5558c26 | |||
| 2670cbf971 | |||
| ee0aecd7ca | |||
| a27f861edf | |||
| 838c2afa68 | |||
| 2449eeb26f | |||
| 3869ff11ed | |||
| 45edc73aa2 | |||
| d0b791287f | |||
| df29d66f15 | |||
| 06b5b017e4 | |||
| 64978d3245 | |||
| 01eabf2655 | |||
| 9de2a5fb39 | |||
|
|
27865cf7d4 | ||
|
|
4f28fd2015 | ||
|
|
fd110e4193 | ||
|
|
82f0fc13e9 | ||
| 6932687d9c | |||
|
|
014ed32f08 | ||
| 66379d36b5 | |||
| d04641b742 | |||
|
|
7a147d6cb4 | ||
|
|
c660025b78 | ||
| 59343dd756 | |||
| 5a370d5c38 | |||
| 7082d8c91e | |||
| 4de79db243 | |||
| c20f64d1e0 | |||
| 9536ba1378 | |||
| 51664b3273 | |||
| dd23a3943f | |||
| dd70b0b988 | |||
|
|
6ea13ecb26 | ||
|
|
d4fcd67dde | ||
|
|
d694f5ce0b | ||
|
|
849eaf018d | ||
| dabd0acd9e | |||
| 8c230c900c | |||
| 67a61da62b | |||
| 6d20cba4ae | |||
| b822b9c524 | |||
| a2c0cb7e8a | |||
| 5212aff3d6 | |||
| e2c95c6096 | |||
| 94167bedca | |||
| 0f511b0572 | |||
|
|
8c7cc741fe | ||
| eab701c381 | |||
| fa44cff7df | |||
|
|
cf755242e1 | ||
| 51cae5d065 | |||
|
|
f788fb2c8b | ||
|
|
634b76c127 | ||
| 95dd862d49 | |||
| 88fe6c89c8 | |||
| 2249493cc9 | |||
| 87d8fa16d7 | |||
| 9696f6c5f1 | |||
| d302eea649 | |||
| 0b78ffd2c8 | |||
|
|
d5f28df86a | ||
|
|
1a562a7594 | ||
|
|
e0e7f9eb57 | ||
| a051b1ab33 | |||
| 67aed44db0 | |||
| cfa0475158 | |||
| 550af0164c | |||
| 303da5bdc5 | |||
|
|
d5fd0a13aa | ||
|
|
30a3106c29 | ||
|
|
185b34ea8a | ||
|
|
674eac23df | ||
|
|
89c11085ba | ||
|
|
f3978396eb | ||
|
|
f08b18dc34 | ||
|
|
4f147b1f47 | ||
|
|
e90f8d280b | ||
|
|
5fde0be53d | ||
|
|
fcf629d82d | ||
|
|
ca14a24f91 | ||
|
|
c342aee89c | ||
|
|
061eb851af | ||
|
|
92b22e1539 | ||
| e306180a73 | |||
| b367852062 | |||
| b6c595a11a | |||
| bb1a35103e | |||
| 133d076666 | |||
| 57c743c253 | |||
| 617d205458 | |||
| d1d1c8cdda | |||
| dde8ede88d | |||
| c24592d4e2 | |||
| e77bf45914 | |||
| 3fbb3cf061 | |||
| f62aa21088 | |||
| 44b3b4938b | |||
| fc40a296f9 | |||
| 101a3d7bd2 | |||
| f7e5e49bf5 | |||
| 8468109b7c | |||
| 56d69110d4 | |||
| 4f0127dbd8 | |||
| 4359668699 | |||
| f387b9020b | |||
| 164bcbb260 | |||
| 90e28edfbe | |||
| b7a6bcc77f | |||
| 20cf87fb40 | |||
| d263bab16d | |||
| 2052f2c4b8 | |||
| b82a6e6f0d | |||
| f42be3b51a | |||
| 169bdb376c | |||
| 0badfa58a5 | |||
| 1c210a9a0e | |||
| 1ba3f2e17e | |||
| d3e85a11e0 | |||
| e24cb4fac4 | |||
| 65a735d8a4 | |||
|
|
7103ff2e5b | ||
|
|
9ada5f7c9f | ||
|
|
b8b0e6d50f | ||
|
|
4cef754f29 | ||
|
|
d0130e9280 | ||
|
|
980da05ed5 | ||
|
|
30d416cfac | ||
| 15ec46afb3 | |||
| 7ca6fec5f6 | |||
| 6c9a56505a | |||
| 57fa3a3232 | |||
| 809294edae | |||
| 15402bc076 | |||
| 666638009e | |||
| e2912c5b53 | |||
| e38b44c9c7 | |||
| 166f66b761 | |||
| af1a7f60ab | |||
| fccea76cef | |||
| a830e4d7a2 | |||
| 66b713028b | |||
| 9a0d642c3c | |||
| b1331cab37 | |||
| 03112f53fa | |||
| cd70aa81c6 | |||
| 608ddb6c48 | |||
| fc641dda09 | |||
| f0dafc0e70 | |||
| 2dfa9768ec | |||
| 31466e3743 | |||
| 0895815b29 | |||
| 049944be2a | |||
| 0a64c55bdc | |||
| ac37de72ba | |||
| 0506c25873 | |||
| a2f0271d0f | |||
| 09a773a602 | |||
| c2a18971ab | |||
| e0039ceeff | |||
| 9a7b0ce54f | |||
| 2b906cde43 | |||
| 3b1ea1c519 | |||
| 2e92918882 | |||
| d71c9d311e | |||
| 17d62ab5ad | |||
| 4fc4ce2070 | |||
| e4e9216bdc | |||
| 3151752151 | |||
| c6cbce12cc | |||
| 81899baf6e | |||
| 7c65ec1f5c | |||
| a784a4a037 | |||
| bd4d6d48a0 | |||
| 247e9a1224 | |||
| 96ab380c9e | |||
| c6a7fc6657 | |||
| d1e3d2c791 | |||
| cf00e72307 | |||
| 106ee680c1 | |||
| 89f567e365 | |||
| 4aec0f3336 | |||
| 1feef42010 | |||
| cfa44a996c | |||
| 5770c59bdf | |||
| 2f7370eb01 | |||
| 2ce10515cd | |||
| 98d0fcdebb | |||
| 305a169a0e | |||
| e792b1ac2f | |||
| 221b0dbe36 | |||
| edf667e378 | |||
| cae38a7812 | |||
| a9d5835d7f | |||
| f9461cd010 | |||
| cf54fc4931 | |||
| be98a893a1 | |||
|
|
b515024d07 | ||
| b647f5af59 | |||
| 6849fc4802 | |||
| 6cbe3dc439 | |||
| ddaef4333a | |||
| 289171aac5 | |||
| 6dba583f2c | |||
| 232585fa83 | |||
| 54ab958f19 | |||
| 4faacbc4ed | |||
| ce2b43a84e | |||
| 3f9c9f0ca8 | |||
| 7e00598b8f | |||
| 3714c824e7 | |||
| 3d82bdd4cd | |||
| 033419ac3f | |||
| a94ba3321a | |||
| dec46dcc0a | |||
| 87fa0e9c66 | |||
| 053e635cda | |||
|
|
c7c181f30b | ||
| 82e988c965 | |||
| 58daf860ed | |||
| a095930b97 | |||
| e1ad783b49 | |||
| 76b52b99e4 | |||
| 09e7e59f55 | |||
| f914a177a5 | |||
| c32fd836b5 | |||
| c76eb6f91b | |||
| 0046bde759 | |||
| be927fd028 | |||
| 409b7dde52 | |||
| eeb0e16665 | |||
| 1d224e6171 | |||
| 274784ea0a | |||
| d43389ec5e | |||
| ff0f375cb3 | |||
| ee11ed639f | |||
| b07adc7cb4 | |||
| 40866a3eb3 | |||
| 03c6ce2dbd | |||
| 155b75f45d | |||
| 9f4b31ab1f | |||
| 39816756a3 | |||
| e04af3d044 | |||
| db1243ce91 | |||
| 566d89fda2 | |||
| c7229154ca | |||
| e19ddcdcb1 | |||
|
|
b002e85958 | ||
| 38947e9174 | |||
| 9ab957e484 | |||
| 68fa9dfbee | |||
| 95d00a8942 | |||
| ace43f35b0 | |||
| 5831480b1b | |||
| e9dc413227 | |||
| 147e3da053 | |||
| f1ed7c9c9a | |||
| a7cdcd99b9 | |||
| 2833af077e | |||
| 11f600ea1c | |||
| f6478ee72e | |||
|
|
d232cc536d | ||
| fa690cac57 | |||
| 22ccd192ba | |||
| 798e9ae052 | |||
| b4f085d0f9 | |||
| 7f1acffb4c | |||
| 74b9fc8790 | |||
| 0f97a32fed | |||
| c3ed1e09f1 | |||
| 85a4936d4c | |||
| 5ed12f966f | |||
| a597b5e46f | |||
|
|
da3aad1c55 | ||
| a160c41a66 | |||
| 2b8170c6c8 | |||
| 8a6733893e | |||
| fd715f4191 | |||
| 0a2e237e5b | |||
|
|
f2df567d0f | ||
| 6d5dce4306 | |||
|
|
4cb30a7747 | ||
| a741e56d3c | |||
| c5bf9aacd5 | |||
|
|
cd62d229a5 | ||
| 2f56564cd9 | |||
| 8b4083725b | |||
| 3954dd5218 | |||
| 6716bd1d0b | |||
| 6e4ae1835b | |||
|
|
94a9cba182 | ||
|
|
25341dc814 | ||
| fe9ce65eb2 | |||
| 405d1e583b | |||
| 74807f5514 | |||
|
|
cb02fb20fe | ||
|
|
94bc3b8524 | ||
|
|
7026ad395d | ||
|
|
05c9a03eea | ||
| 3a251f06c7 | |||
| d396e7391b | |||
| 40643fd7a7 | |||
| 72d9afb7c5 | |||
| ea742e432f | |||
| 5fbb0eee9c | |||
| e4c7e2d97a | |||
|
|
dd4eca8407 | ||
|
|
5d94d3f44e | ||
|
|
b383ed1cdc | ||
|
|
9bdefcc922 | ||
|
|
0b092e8d26 | ||
|
|
b984ec3569 | ||
|
|
9c1cd94e87 | ||
|
|
8124c755f8 | ||
| 937738c8eb | |||
| 34d23ee54b | |||
| 65e95f876b | |||
| 0352511b04 | |||
| ced958df1e | |||
| ec3d83df97 | |||
| 3f8ed5a93a | |||
| cb178427ca | |||
| d51f3fe51e | |||
| 191fc23b52 | |||
|
|
7e84caffc4 | ||
| 84e5d1e763 | |||
| cb970daadf | |||
| ffb0de63df | |||
| edea3cf379 | |||
| 67897bd14b | |||
|
|
b16d3da3e2 | ||
|
|
83651244fe | ||
|
|
e590e7d02d | ||
|
|
dc7f07b42b | ||
|
|
de73e9247b | ||
| a088b72b20 | |||
| 3438fa1fe9 | |||
| ad2d65b1cc | |||
| 557d45e06e | |||
| dfdee49e2d | |||
| f5b0fec46f | |||
| e4423a3be8 | |||
| eeb28d8b6a | |||
| b3e4b6149e | |||
| c98c8a472b | |||
|
|
666e6ecd41 | ||
| 062d3b983e | |||
| 197c266cd4 | |||
| b874ac28aa | |||
|
|
88bcf8a761 | ||
| 4cef8060a3 | |||
| 50798eb572 | |||
| 7067bfa12a | |||
| 64f43373df | |||
| da65d02bd7 | |||
| 0441c5076a | |||
| 28de33a83d | |||
| 49e91066d4 | |||
| bb28c3dd2f | |||
| 1ebec4223a | |||
| e868e94fcd | |||
| 0e84534109 | |||
| 8c642fea20 | |||
| 7dbd3075b6 | |||
| 050462d011 | |||
| ba244f24bb | |||
| 39fe26b3f1 | |||
| 5de2cca73e | |||
| 8c17e92dd8 | |||
| b1cb2491d8 | |||
| a5ee4d9ae5 | |||
| fb8f0c47f5 | |||
| 099ec9f5e3 | |||
| 906250c019 | |||
| 9b3a09740d | |||
| 15a055919c | |||
| d7c168cd1e | |||
| 01acee8ea5 | |||
| 77bc58e44b | |||
| 68114824da | |||
| 3f89175175 | |||
| 3360e2dc8f | |||
| 91b3c9a007 | |||
| fae14e7d1f | |||
| 776ce4eec6 | |||
| c61dbc01a3 | |||
| 44552108d4 | |||
| ff1063d866 | |||
| 38d4ae0d5e | |||
| 7adc1e91f5 | |||
| d2b04e6f0e | |||
| 0206cfd65f | |||
| 89f2fa1162 | |||
|
|
7278f7d9db | ||
|
|
14272a7c25 | ||
|
|
e613581d34 | ||
| cdfdb49be1 | |||
| 52c001a380 | |||
| 9dceb29513 | |||
| f409499159 | |||
| 59076d3df1 | |||
| 3dbf137752 | |||
| 918536d2ed | |||
| 812f0acac2 | |||
| 8a30aff913 | |||
| d157503ef2 | |||
| 8d4e9ebeca | |||
| ec9b3fb7c6 | |||
| a29c7d7af6 | |||
| ef189db30c | |||
| 110e13b31e | |||
| 5640088b5a | |||
| d2b26c6f9e | |||
| a48c0abf0d | |||
| 6ec811b963 | |||
| 5fc3c42694 | |||
| 16808ba847 | |||
| e19c3b7841 | |||
| 88aa5a412f | |||
| 61f966e163 | |||
| 8c4322f768 | |||
| b85b706169 | |||
| 8198cfb061 | |||
| 7b7e8e93d4 | |||
| b83848116b | |||
| 036c08c2d9 | |||
| 2bf7ca3c92 | |||
|
|
089b6906b9 | ||
| 786881334d | |||
|
|
ddf8ef5105 | ||
| c121e51b1f | |||
| 3e175eb367 | |||
| 3e5ee4a5a4 | |||
| aa6fbb0692 | |||
|
|
9eedcecf0c | ||
| 2204ca3dc8 | |||
| 764e6a3a2c | |||
| ec48b13150 | |||
| 3cfd61e6cc | |||
|
|
57c811ece4 | ||
| 0d86929b62 | |||
| e442988725 | |||
| 1a041a4dac | |||
| af2f607a3b | |||
| 96532a99fb | |||
| 809f768703 | |||
| 154b9b8b65 | |||
| aeb608a5d3 | |||
| f541180fcf | |||
| 134b330145 | |||
|
|
7c9a1f8b51 | ||
|
|
1fd05fbf25 | ||
|
|
ae9b2a46b6 | ||
| 8dc1d57252 | |||
|
|
d7a13c9fe6 | ||
| 365f830e05 | |||
|
|
9cf5ffa339 | ||
|
|
568d2cbf23 |
|
|
@ -0,0 +1,6 @@
|
||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<settings>
|
||||||
|
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||||
|
<version value="1.0" />
|
||||||
|
</settings>
|
||||||
|
</component>
|
||||||
14
BolonkinNM/.idea/maze_project_submission.iml
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="PYTHON_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/venv" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
<component name="PyDocumentationSettings">
|
||||||
|
<option name="format" value="PLAIN" />
|
||||||
|
<option name="myDocStringFormat" value="Plain" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
4
BolonkinNM/.idea/misc.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.11 (maze_project_submission) (2)" project-jdk-type="Python SDK" />
|
||||||
|
</project>
|
||||||
8
BolonkinNM/.idea/modules.xml
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/maze_project_submission.iml" filepath="$PROJECT_DIR$/.idea/maze_project_submission.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
42
BolonkinNM/.idea/workspace.xml
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="AutoImportSettings">
|
||||||
|
<option name="autoReloadType" value="SELECTIVE" />
|
||||||
|
</component>
|
||||||
|
<component name="ChangeListManager">
|
||||||
|
<list default="true" id="a6ff989d-c5f6-4522-8b0a-933849f2d044" name="Changes" comment="" />
|
||||||
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
|
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||||
|
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||||
|
</component>
|
||||||
|
<component name="MarkdownSettingsMigration">
|
||||||
|
<option name="stateVersion" value="1" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectColorInfo"><![CDATA[{
|
||||||
|
"associatedIndex": 2
|
||||||
|
}]]></component>
|
||||||
|
<component name="ProjectId" id="3EB20Mq0B865MSq8Kkl2evaRIZW" />
|
||||||
|
<component name="ProjectViewState">
|
||||||
|
<option name="hideEmptyMiddlePackages" value="true" />
|
||||||
|
<option name="showLibraryContents" value="true" />
|
||||||
|
</component>
|
||||||
|
<component name="PropertiesComponent"><![CDATA[{
|
||||||
|
"keyToString": {
|
||||||
|
"RunOnceActivity.OpenProjectViewOnStart": "true",
|
||||||
|
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||||
|
"last_opened_file_path": "C:/Users/vaz21/Downloads/Task 2 GLOBAL/maze_project_submission"
|
||||||
|
}
|
||||||
|
}]]></component>
|
||||||
|
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
|
||||||
|
<component name="TaskManager">
|
||||||
|
<task active="true" id="Default" summary="Default task">
|
||||||
|
<changelist id="a6ff989d-c5f6-4522-8b0a-933849f2d044" name="Changes" comment="" />
|
||||||
|
<created>1779637417749</created>
|
||||||
|
<option name="number" value="Default" />
|
||||||
|
<option name="presentableId" value="Default" />
|
||||||
|
<updated>1779637417749</updated>
|
||||||
|
</task>
|
||||||
|
<servers />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
24
BolonkinNM/README.md
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
# Maze Solver Project
|
||||||
|
|
||||||
|
ООП-проект для поиска выхода из лабиринта с паттернами:
|
||||||
|
- Builder
|
||||||
|
- Strategy
|
||||||
|
- Observer
|
||||||
|
- Command
|
||||||
|
|
||||||
|
## Запуск
|
||||||
|
```bash
|
||||||
|
python main.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## Эксперименты
|
||||||
|
```bash
|
||||||
|
python experiment.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Результаты сохраняются в папку `experiment_results/`.
|
||||||
|
|
||||||
|
## Требования
|
||||||
|
```bash
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
7
BolonkinNM/builders/maze_builder.py
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
|
||||||
|
|
||||||
|
class MazeBuilder(ABC):
|
||||||
|
@abstractmethod
|
||||||
|
def buildFromFile(self, filename):
|
||||||
|
raise NotImplementedError
|
||||||
52
BolonkinNM/builders/text_file_maze_builder.py
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
from core.cell import Cell
|
||||||
|
from core.maze import Maze
|
||||||
|
from builders.maze_builder import MazeBuilder
|
||||||
|
|
||||||
|
|
||||||
|
class TextFileMazeBuilder(MazeBuilder):
|
||||||
|
def buildFromFile(self, filename):
|
||||||
|
with open(filename, "r", encoding="utf-8") as f:
|
||||||
|
lines = [line.rstrip("\n") for line in f]
|
||||||
|
|
||||||
|
if not lines:
|
||||||
|
raise ValueError("Maze file is empty")
|
||||||
|
|
||||||
|
width = max(len(line) for line in lines)
|
||||||
|
height = len(lines)
|
||||||
|
|
||||||
|
cells = []
|
||||||
|
startCell = None
|
||||||
|
exitCell = None
|
||||||
|
|
||||||
|
for y, line in enumerate(lines):
|
||||||
|
row = []
|
||||||
|
for x in range(width):
|
||||||
|
ch = line[x] if x < len(line) else "#"
|
||||||
|
|
||||||
|
if ch == "#":
|
||||||
|
cell = Cell(x, y, isWall=True)
|
||||||
|
elif ch == "S":
|
||||||
|
if startCell is not None:
|
||||||
|
raise ValueError("Multiple start cells found")
|
||||||
|
cell = Cell(x, y, isWall=False, isStart=True)
|
||||||
|
startCell = cell
|
||||||
|
elif ch == "E":
|
||||||
|
if exitCell is not None:
|
||||||
|
raise ValueError("Multiple exit cells found")
|
||||||
|
cell = Cell(x, y, isWall=False, isExit=True)
|
||||||
|
exitCell = cell
|
||||||
|
elif ch in (" ", "."):
|
||||||
|
cell = Cell(x, y, isWall=False)
|
||||||
|
elif ch.isdigit():
|
||||||
|
cell = Cell(x, y, isWall=False, weight=max(1, int(ch)))
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Unsupported symbol '{ch}' at ({x}, {y})")
|
||||||
|
row.append(cell)
|
||||||
|
cells.append(row)
|
||||||
|
|
||||||
|
if startCell is None:
|
||||||
|
raise ValueError("Start cell 'S' not found")
|
||||||
|
if exitCell is None:
|
||||||
|
raise ValueError("Exit cell 'E' not found")
|
||||||
|
|
||||||
|
return Maze(cells, width, height, startCell, exitCell)
|
||||||
0
BolonkinNM/commands/__init__.py
Normal file
11
BolonkinNM/commands/command.py
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
|
||||||
|
|
||||||
|
class Command(ABC):
|
||||||
|
@abstractmethod
|
||||||
|
def execute(self):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def undo(self):
|
||||||
|
raise NotImplementedError
|
||||||
37
BolonkinNM/commands/move_command.py
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
from commands.command import Command
|
||||||
|
|
||||||
|
|
||||||
|
class MoveCommand(Command):
|
||||||
|
DIRECTION_TO_DELTA = {
|
||||||
|
"W": (0, -1),
|
||||||
|
"A": (-1, 0),
|
||||||
|
"S": (0, 1),
|
||||||
|
"D": (1, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, player, maze, direction):
|
||||||
|
self.player = player
|
||||||
|
self.maze = maze
|
||||||
|
self.direction = direction.upper()
|
||||||
|
self.previousCell = None
|
||||||
|
|
||||||
|
def execute(self):
|
||||||
|
if self.direction not in self.DIRECTION_TO_DELTA:
|
||||||
|
return False
|
||||||
|
|
||||||
|
dx, dy = self.DIRECTION_TO_DELTA[self.direction]
|
||||||
|
current = self.player.currentCell
|
||||||
|
new_cell = self.maze.getCell(current.x + dx, current.y + dy)
|
||||||
|
|
||||||
|
if new_cell is None or not new_cell.isPassable():
|
||||||
|
return False
|
||||||
|
|
||||||
|
self.previousCell = current
|
||||||
|
self.player.setCell(new_cell)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def undo(self):
|
||||||
|
if self.previousCell is None:
|
||||||
|
return False
|
||||||
|
self.player.setCell(self.previousCell)
|
||||||
|
return True
|
||||||
0
BolonkinNM/controller/__init__.py
Normal file
30
BolonkinNM/controller/game_controller.py
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
from commands.move_command import MoveCommand
|
||||||
|
|
||||||
|
|
||||||
|
class GameController:
|
||||||
|
def __init__(self, maze, player, view):
|
||||||
|
self.maze = maze
|
||||||
|
self.player = player
|
||||||
|
self.view = view
|
||||||
|
self.history = []
|
||||||
|
|
||||||
|
def move(self, direction):
|
||||||
|
command = MoveCommand(self.player, self.maze, direction)
|
||||||
|
if command.execute():
|
||||||
|
self.history.append(command)
|
||||||
|
self.view.update({"type": "move", "direction": direction})
|
||||||
|
self.view.render(self.maze, player_position=self.player.currentCell)
|
||||||
|
return True
|
||||||
|
print("Cannot move there")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def undo(self):
|
||||||
|
if not self.history:
|
||||||
|
print("Nothing to undo")
|
||||||
|
return False
|
||||||
|
command = self.history.pop()
|
||||||
|
if command.undo():
|
||||||
|
self.view.update({"type": "undo"})
|
||||||
|
self.view.render(self.maze, player_position=self.player.currentCell)
|
||||||
|
return True
|
||||||
|
return False
|
||||||
0
BolonkinNM/core/__init__.py
Normal file
26
BolonkinNM/core/cell.py
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Cell:
|
||||||
|
x: int
|
||||||
|
y: int
|
||||||
|
isWall: bool = False
|
||||||
|
isStart: bool = False
|
||||||
|
isExit: bool = False
|
||||||
|
weight: int = 1
|
||||||
|
|
||||||
|
def isPassable(self):
|
||||||
|
return not self.isWall
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
parts = [f"Cell({self.x}, {self.y}"]
|
||||||
|
if self.isWall:
|
||||||
|
parts.append("WALL")
|
||||||
|
if self.isStart:
|
||||||
|
parts.append("START")
|
||||||
|
if self.isExit:
|
||||||
|
parts.append("EXIT")
|
||||||
|
if self.weight != 1:
|
||||||
|
parts.append(f"w={self.weight}")
|
||||||
|
return ", ".join(parts) + ")"
|
||||||
49
BolonkinNM/core/maze.py
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
class Maze:
|
||||||
|
def __init__(self, cells, width, height, startCell=None, exitCell=None):
|
||||||
|
self.cells = cells
|
||||||
|
self.width = width
|
||||||
|
self.height = height
|
||||||
|
self.startCell = startCell
|
||||||
|
self.exitCell = exitCell
|
||||||
|
|
||||||
|
def getCell(self, x, y):
|
||||||
|
if 0 <= x < self.width and 0 <= y < self.height:
|
||||||
|
return self.cells[y][x]
|
||||||
|
return None
|
||||||
|
|
||||||
|
def getNeighbors(self, cell):
|
||||||
|
neighbors = []
|
||||||
|
for dx, dy in ((0, -1), (0, 1), (-1, 0), (1, 0)):
|
||||||
|
nx, ny = cell.x + dx, cell.y + dy
|
||||||
|
neighbor = self.getCell(nx, ny)
|
||||||
|
if neighbor is not None and neighbor.isPassable():
|
||||||
|
neighbors.append(neighbor)
|
||||||
|
return neighbors
|
||||||
|
|
||||||
|
def render_lines(self, player_position=None, path=None):
|
||||||
|
path_set = {(c.x, c.y) for c in path} if path else set()
|
||||||
|
player_pos = None if player_position is None else (player_position.x, player_position.y)
|
||||||
|
lines = []
|
||||||
|
for y in range(self.height):
|
||||||
|
row = []
|
||||||
|
for x in range(self.width):
|
||||||
|
cell = self.cells[y][x]
|
||||||
|
if player_pos == (x, y):
|
||||||
|
row.append("P")
|
||||||
|
elif cell.isStart:
|
||||||
|
row.append("S")
|
||||||
|
elif cell.isExit:
|
||||||
|
row.append("E")
|
||||||
|
elif cell.isWall:
|
||||||
|
row.append("#")
|
||||||
|
elif (x, y) in path_set:
|
||||||
|
row.append("*")
|
||||||
|
elif cell.weight > 1:
|
||||||
|
row.append(str(cell.weight))
|
||||||
|
else:
|
||||||
|
row.append(" ")
|
||||||
|
lines.append("".join(row))
|
||||||
|
return lines
|
||||||
|
|
||||||
|
def render(self, player_position=None, path=None):
|
||||||
|
return "\n".join(self.render_lines(player_position=player_position, path=path))
|
||||||
6
BolonkinNM/core/player.py
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
class Player:
|
||||||
|
def __init__(self, currentCell):
|
||||||
|
self.currentCell = currentCell
|
||||||
|
|
||||||
|
def setCell(self, cell):
|
||||||
|
self.currentCell = cell
|
||||||
11
BolonkinNM/core/search_stats.py
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class SearchStats:
|
||||||
|
timeMs: float
|
||||||
|
visitedCells: int
|
||||||
|
pathLength: int
|
||||||
|
path: list = field(default_factory=list)
|
||||||
|
found: bool = False
|
||||||
|
algorithm: str = ""
|
||||||
1
BolonkinNM/docs/README.txt
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Place report files and experiment outputs here.
|
||||||
249
BolonkinNM/docs/report.md
Normal file
|
|
@ -0,0 +1,249 @@
|
||||||
|
# Отчёт по работе «Поиск выхода из лабиринта»
|
||||||
|
|
||||||
|
## 1. Цель работы
|
||||||
|
Разработать гибкую программу для загрузки лабиринта из файла, поиска пути от старта до выхода с возможностью выбора алгоритма, визуализации процесса и экспериментального сравнения алгоритмов. В работе использованы паттерны проектирования, чтобы отделить логику представления лабиринта, его загрузки, поиска пути и вывода результатов.
|
||||||
|
|
||||||
|
## 2. Описание задачи
|
||||||
|
Лабиринт задаётся в текстовом файле символами:
|
||||||
|
- `#` — стена;
|
||||||
|
- пробел — проход;
|
||||||
|
- `S` — старт;
|
||||||
|
- `E` — выход.
|
||||||
|
|
||||||
|
Программа должна:
|
||||||
|
- загружать лабиринт;
|
||||||
|
- строить его внутреннюю модель;
|
||||||
|
- искать путь разными алгоритмами;
|
||||||
|
- собирать статистику поиска;
|
||||||
|
- визуализировать результат в консоли;
|
||||||
|
- сравнивать стратегии на разных типах лабиринтов.
|
||||||
|
|
||||||
|
## 3. Выбранные паттерны проектирования
|
||||||
|
|
||||||
|
### 3.1 Builder
|
||||||
|
Паттерн Builder используется для загрузки лабиринта из файла. Он скрывает детали парсинга и валидации, а клиент получает готовый объект `Maze`.
|
||||||
|
|
||||||
|
Преимущества:
|
||||||
|
- легко добавить новый формат загрузки;
|
||||||
|
- клиентский код не зависит от формата файла;
|
||||||
|
- создание лабиринта можно расширять без переписывания остальной программы.
|
||||||
|
|
||||||
|
### 3.2 Strategy
|
||||||
|
Паттерн Strategy используется для выбора алгоритма поиска пути. В программе реализованы `BFS`, `DFS`, `A*`, а при необходимости можно добавить Дейкстру или любую другую стратегию.
|
||||||
|
|
||||||
|
Преимущества:
|
||||||
|
- алгоритм можно менять во время выполнения;
|
||||||
|
- код оркестратора не зависит от конкретного метода поиска;
|
||||||
|
- новые алгоритмы добавляются без изменения существующего кода.
|
||||||
|
|
||||||
|
### 3.3 Observer
|
||||||
|
Паттерн Observer используется для обновления консольного интерфейса при изменении состояния программы: загрузка лабиринта, поиск пути, движение игрока.
|
||||||
|
|
||||||
|
Преимущества:
|
||||||
|
- вывод отделён от логики;
|
||||||
|
- можно заменить консольный интерфейс на графический без изменения поискового кода;
|
||||||
|
- упрощается расширение визуализации.
|
||||||
|
|
||||||
|
### 3.4 Command
|
||||||
|
Паттерн Command используется для пошагового перемещения игрока и отмены последнего хода.
|
||||||
|
|
||||||
|
Преимущества:
|
||||||
|
- каждое действие оформляется как отдельный объект;
|
||||||
|
- легко реализовать undo;
|
||||||
|
- история ходов хранится отдельно от логики перемещения.
|
||||||
|
|
||||||
|
## 4. Диаграмма классов
|
||||||
|
Ниже приведена упрощённая диаграмма классов в формате Mermaid:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
classDiagram
|
||||||
|
class Cell {
|
||||||
|
+int x
|
||||||
|
+int y
|
||||||
|
+bool isWall
|
||||||
|
+bool isStart
|
||||||
|
+bool isExit
|
||||||
|
+isPassable()
|
||||||
|
}
|
||||||
|
|
||||||
|
class Maze {
|
||||||
|
+cells
|
||||||
|
+width
|
||||||
|
+height
|
||||||
|
+startCell
|
||||||
|
+exitCell
|
||||||
|
+getCell(x, y)
|
||||||
|
+getNeighbors(cell)
|
||||||
|
}
|
||||||
|
|
||||||
|
class MazeBuilder {
|
||||||
|
<<interface>>
|
||||||
|
+buildFromFile(filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
class TextFileMazeBuilder {
|
||||||
|
+buildFromFile(filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
class PathFindingStrategy {
|
||||||
|
<<interface>>
|
||||||
|
+findPath(maze, start, exitCell)
|
||||||
|
}
|
||||||
|
|
||||||
|
class BFSStrategy {
|
||||||
|
+findPath(maze, start, exitCell)
|
||||||
|
}
|
||||||
|
|
||||||
|
class DFSStrategy {
|
||||||
|
+findPath(maze, start, exitCell)
|
||||||
|
}
|
||||||
|
|
||||||
|
class AStarStrategy {
|
||||||
|
+findPath(maze, start, exitCell)
|
||||||
|
}
|
||||||
|
|
||||||
|
class SearchStats {
|
||||||
|
+timeMs
|
||||||
|
+visitedCells
|
||||||
|
+pathLength
|
||||||
|
+path
|
||||||
|
}
|
||||||
|
|
||||||
|
class MazeSolver {
|
||||||
|
+maze
|
||||||
|
+strategy
|
||||||
|
+setStrategy(strategy)
|
||||||
|
+solve()
|
||||||
|
}
|
||||||
|
|
||||||
|
class Observer {
|
||||||
|
<<interface>>
|
||||||
|
+update(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConsoleView {
|
||||||
|
+update(event)
|
||||||
|
+render(maze, player_position, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
class Command {
|
||||||
|
<<interface>>
|
||||||
|
+execute()
|
||||||
|
+undo()
|
||||||
|
}
|
||||||
|
|
||||||
|
class MoveCommand {
|
||||||
|
+execute()
|
||||||
|
+undo()
|
||||||
|
}
|
||||||
|
|
||||||
|
class Player {
|
||||||
|
+currentCell
|
||||||
|
+setCell(cell)
|
||||||
|
}
|
||||||
|
|
||||||
|
Maze <|-- TextFileMazeBuilder : creates
|
||||||
|
MazeBuilder <|.. TextFileMazeBuilder
|
||||||
|
PathFindingStrategy <|.. BFSStrategy
|
||||||
|
PathFindingStrategy <|.. DFSStrategy
|
||||||
|
PathFindingStrategy <|.. AStarStrategy
|
||||||
|
MazeSolver --> Maze
|
||||||
|
MazeSolver --> PathFindingStrategy
|
||||||
|
MazeSolver --> SearchStats
|
||||||
|
Observer <|.. ConsoleView
|
||||||
|
Command <|.. MoveCommand
|
||||||
|
MoveCommand --> Player
|
||||||
|
MoveCommand --> Maze
|
||||||
|
ConsoleView --> Maze
|
||||||
|
Maze --> Cell
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. Ключевые классы и их роль
|
||||||
|
|
||||||
|
### Cell
|
||||||
|
Хранит координаты клетки и её тип. Позволяет быстро проверять, является ли клетка проходимой.
|
||||||
|
|
||||||
|
### Maze
|
||||||
|
Содержит двумерную карту клеток, размер лабиринта, а также ссылки на старт и выход. Даёт доступ к соседним клеткам по четырём направлениям.
|
||||||
|
|
||||||
|
### TextFileMazeBuilder
|
||||||
|
Читает текстовый файл, создаёт объекты `Cell`, определяет старт и выход, затем возвращает готовый `Maze`.
|
||||||
|
|
||||||
|
### BFSStrategy
|
||||||
|
Ищет кратчайший путь по числу шагов. Подходит для случая, когда все переходы одинаковой стоимости.
|
||||||
|
|
||||||
|
### DFSStrategy
|
||||||
|
Быстро исследует пространство, но не гарантирует кратчайший путь. Полезен как сравнительный алгоритм.
|
||||||
|
|
||||||
|
### AStarStrategy
|
||||||
|
Использует эвристику Манхэттенского расстояния. Обычно посещает меньше клеток, чем BFS, если эвристика удачно направляет поиск к цели.
|
||||||
|
|
||||||
|
### MazeSolver
|
||||||
|
Оркестратор, который хранит лабиринт и текущую стратегию. Вызывает поиск, измеряет время и собирает статистику.
|
||||||
|
|
||||||
|
### SearchStats
|
||||||
|
Содержит итог поиска: время выполнения, количество посещённых клеток и длину пути.
|
||||||
|
|
||||||
|
### ConsoleView
|
||||||
|
Реализует наблюдателя и умеет выводить лабиринт и найденный путь в консоль.
|
||||||
|
|
||||||
|
### MoveCommand
|
||||||
|
Оформляет ход игрока как объект-команду. Поддерживает отмену последнего перемещения.
|
||||||
|
|
||||||
|
## 6. Экспериментальная часть
|
||||||
|
|
||||||
|
### 6.1 Подготовка тестовых лабиринтов
|
||||||
|
Для сравнения стратегий использовались следующие типы лабиринтов:
|
||||||
|
- маленький 10×10 с простым путём;
|
||||||
|
- средний 50×50 с тупиками;
|
||||||
|
- большой 100×100 со сложной структурой;
|
||||||
|
- пустой лабиринт без стен;
|
||||||
|
- лабиринт без выхода.
|
||||||
|
|
||||||
|
### 6.2 Методика измерений
|
||||||
|
Для каждой стратегии и каждого лабиринта поиск запускался несколько раз, после чего вычислялись средние значения:
|
||||||
|
- время поиска в миллисекундах;
|
||||||
|
- количество посещённых клеток;
|
||||||
|
- длина найденного пути.
|
||||||
|
|
||||||
|
Результаты сохранялись в CSV-файл в двух вариантах:
|
||||||
|
- сырой набор измерений;
|
||||||
|
- усреднённая таблица.
|
||||||
|
|
||||||
|
## 7. Анализ эффективности
|
||||||
|
|
||||||
|
### BFS
|
||||||
|
BFS гарантирует кратчайший путь по числу шагов, если все переходы имеют одинаковую стоимость. На простых и пустых лабиринтах работает стабильно и предсказуемо. Минус — может посещать много клеток, особенно на больших лабиринтах.
|
||||||
|
|
||||||
|
### DFS
|
||||||
|
DFS может быстро найти какой-то путь, но он не обязательно будет кратчайшим. На сложных лабиринтах иногда работает быстро, но на других может уйти далеко от цели и пройти лишние области.
|
||||||
|
|
||||||
|
### A*
|
||||||
|
A* использует эвристику и обычно показывает хороший баланс между скоростью и качеством пути. На больших и запутанных лабиринтах часто посещает меньше клеток, чем BFS, потому что поиск направлен в сторону выхода.
|
||||||
|
|
||||||
|
### Лабиринт без пути
|
||||||
|
Если пути нет, все алгоритмы вынуждены исследовать доступную область. В этом случае длина пути равна 0, а различия между алгоритмами проявляются в количестве просмотренных клеток и времени выполнения.
|
||||||
|
|
||||||
|
### Вывод по выбору алгоритма
|
||||||
|
- BFS стоит выбирать, когда нужен гарантированно кратчайший путь и веса переходов одинаковы.
|
||||||
|
- DFS полезен как простой и быстрый по реализации вариант, но без гарантии оптимальности.
|
||||||
|
- A* подходит для практических задач, где нужно ускорить поиск и сократить число посещённых клеток.
|
||||||
|
- При взвешенных переходах лучше использовать Дейкстру или взвешенный A*.
|
||||||
|
|
||||||
|
## 8. Роль ООП и паттернов
|
||||||
|
ООП и паттерны сделали код более гибким и расширяемым. Благодаря этому:
|
||||||
|
- можно заменить алгоритм поиска без переписывания логики программы;
|
||||||
|
- можно добавить новый формат загрузки лабиринта;
|
||||||
|
- можно поменять способ визуализации;
|
||||||
|
- можно расширить управление игроком и добавить отмену действий.
|
||||||
|
|
||||||
|
Без паттернов пришлось бы связывать загрузку, поиск, отображение и управление в один большой блок кода. Это усложнило бы отладку и дальнейшие изменения.
|
||||||
|
|
||||||
|
## 9. Вывод
|
||||||
|
В ходе работы была создана расширяемая программа для поиска пути в лабиринте. Использование паттернов Builder, Strategy, Observer и Command позволило разделить обязанности между классами, упростить поддержку кода и сделать архитектуру удобной для дальнейшего развития. Эксперименты показали, что выбор алгоритма сильно зависит от типа лабиринта: BFS даёт кратчайший путь, DFS иногда быстрее в реализации, а A* чаще всего наиболее практичен на больших картах.
|
||||||
|
|
||||||
|
## 10. Приложения
|
||||||
|
- Листинги ключевых классов.
|
||||||
|
- CSV-файлы с результатами экспериментов.
|
||||||
|
- Графики сравнений.
|
||||||
|
- Файлы с тестовыми лабиринтами.
|
||||||
225
BolonkinNM/experiment.py
Normal file
|
|
@ -0,0 +1,225 @@
|
||||||
|
from pathlib import Path
|
||||||
|
from statistics import mean
|
||||||
|
import csv
|
||||||
|
import random
|
||||||
|
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
|
||||||
|
from core.cell import Cell
|
||||||
|
from core.maze import Maze
|
||||||
|
from solver.maze_solver import MazeSolver
|
||||||
|
from strategies.astar_strategy import AStarStrategy
|
||||||
|
from strategies.bfs_strategy import BFSStrategy
|
||||||
|
from strategies.dfs_strategy import DFSStrategy
|
||||||
|
from strategies.dijkstra_strategy import DijkstraStrategy
|
||||||
|
|
||||||
|
|
||||||
|
BASE_DIR = Path(__file__).resolve().parent
|
||||||
|
OUT_DIR = BASE_DIR / "experiment_results"
|
||||||
|
|
||||||
|
|
||||||
|
def build_maze_from_symbols(lines):
|
||||||
|
height = len(lines)
|
||||||
|
width = max(len(line) for line in lines)
|
||||||
|
cells = []
|
||||||
|
start = None
|
||||||
|
exit_cell = None
|
||||||
|
for y, line in enumerate(lines):
|
||||||
|
row = []
|
||||||
|
for x in range(width):
|
||||||
|
ch = line[x] if x < len(line) else "#"
|
||||||
|
if ch == "#":
|
||||||
|
cell = Cell(x, y, isWall=True)
|
||||||
|
elif ch == "S":
|
||||||
|
cell = Cell(x, y, isWall=False, isStart=True)
|
||||||
|
start = cell
|
||||||
|
elif ch == "E":
|
||||||
|
cell = Cell(x, y, isWall=False, isExit=True)
|
||||||
|
exit_cell = cell
|
||||||
|
elif ch == " " or ch == ".":
|
||||||
|
cell = Cell(x, y, isWall=False)
|
||||||
|
elif ch.isdigit():
|
||||||
|
cell = Cell(x, y, isWall=False, weight=int(ch))
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Unknown symbol '{ch}' at {x},{y}")
|
||||||
|
row.append(cell)
|
||||||
|
cells.append(row)
|
||||||
|
return Maze(cells, width, height, start, exit_cell)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_empty_maze(width, height):
|
||||||
|
lines = [" " * width for _ in range(height)]
|
||||||
|
lines = [list(row) for row in lines]
|
||||||
|
lines[1][1] = "S"
|
||||||
|
lines[height - 2][width - 2] = "E"
|
||||||
|
return build_maze_from_symbols(["".join(row) for row in lines])
|
||||||
|
|
||||||
|
|
||||||
|
def generate_simple_maze(width, height):
|
||||||
|
grid = [["#" for _ in range(width)] for _ in range(height)]
|
||||||
|
for x in range(1, width - 1):
|
||||||
|
grid[1][x] = " "
|
||||||
|
for y in range(1, height - 1):
|
||||||
|
grid[y][width - 2] = " "
|
||||||
|
grid[1][1] = "S"
|
||||||
|
grid[height - 2][width - 2] = "E"
|
||||||
|
return build_maze_from_symbols(["".join(row) for row in grid])
|
||||||
|
|
||||||
|
|
||||||
|
def generate_branching_maze(width, height, seed=42, wall_density=0.30):
|
||||||
|
rng = random.Random(seed)
|
||||||
|
grid = [["#" for _ in range(width)] for _ in range(height)]
|
||||||
|
x, y = 1, 1
|
||||||
|
grid[y][x] = "S"
|
||||||
|
while (x, y) != (width - 2, height - 2):
|
||||||
|
candidates = []
|
||||||
|
for dx, dy in [(1, 0), (0, 1)]:
|
||||||
|
nx, ny = x + dx, y + dy
|
||||||
|
if 1 <= nx < width - 1 and 1 <= ny < height - 1:
|
||||||
|
candidates.append((nx, ny))
|
||||||
|
if not candidates:
|
||||||
|
break
|
||||||
|
x, y = rng.choice(candidates)
|
||||||
|
grid[y][x] = " "
|
||||||
|
grid[height - 2][width - 2] = "E"
|
||||||
|
|
||||||
|
# carve extra corridors and dead ends
|
||||||
|
for yy in range(1, height - 1):
|
||||||
|
for xx in range(1, width - 1):
|
||||||
|
if grid[yy][xx] == "#" and rng.random() > wall_density:
|
||||||
|
grid[yy][xx] = " "
|
||||||
|
grid[1][1] = "S"
|
||||||
|
grid[height - 2][width - 2] = "E"
|
||||||
|
return build_maze_from_symbols(["".join(row) for row in grid])
|
||||||
|
|
||||||
|
|
||||||
|
def generate_no_path_maze(width, height):
|
||||||
|
grid = [[" " for _ in range(width)] for _ in range(height)]
|
||||||
|
for x in range(width):
|
||||||
|
grid[height // 2][x] = "#"
|
||||||
|
grid[1][1] = "S"
|
||||||
|
grid[height - 2][width - 2] = "E"
|
||||||
|
return build_maze_from_symbols(["".join(row) for row in grid])
|
||||||
|
|
||||||
|
|
||||||
|
def generate_weighted_maze(width, height, seed=123):
|
||||||
|
rng = random.Random(seed)
|
||||||
|
grid = [[" " for _ in range(width)] for _ in range(height)]
|
||||||
|
for y in range(height):
|
||||||
|
for x in range(width):
|
||||||
|
r = rng.random()
|
||||||
|
if r < 0.12:
|
||||||
|
grid[y][x] = "#"
|
||||||
|
elif r < 0.25:
|
||||||
|
grid[y][x] = "3"
|
||||||
|
elif r < 0.40:
|
||||||
|
grid[y][x] = "2"
|
||||||
|
else:
|
||||||
|
grid[y][x] = "1"
|
||||||
|
# ensure path-ish
|
||||||
|
for x in range(width):
|
||||||
|
grid[1][x] = "1"
|
||||||
|
for y in range(1, height):
|
||||||
|
grid[y][width - 2] = "1"
|
||||||
|
grid[1][1] = "S"
|
||||||
|
grid[height - 2][width - 2] = "E"
|
||||||
|
return build_maze_from_symbols(["".join(row) for row in grid])
|
||||||
|
|
||||||
|
|
||||||
|
def bench_one_maze(maze_name, maze, strategies, repeats=5):
|
||||||
|
summary_rows = []
|
||||||
|
raw_rows = []
|
||||||
|
for strategy_name, strategy_factory in strategies:
|
||||||
|
times, visiteds, lengths = [], [], []
|
||||||
|
for run in range(1, repeats + 1):
|
||||||
|
solver = MazeSolver(maze)
|
||||||
|
solver.setStrategy(strategy_factory())
|
||||||
|
stats = solver.solve()
|
||||||
|
raw_rows.append([maze_name, strategy_name, run, f"{stats.timeMs:.6f}", stats.visitedCells, stats.pathLength])
|
||||||
|
times.append(stats.timeMs)
|
||||||
|
visiteds.append(stats.visitedCells)
|
||||||
|
lengths.append(stats.pathLength)
|
||||||
|
summary_rows.append([maze_name, strategy_name, f"{mean(times):.6f}", f"{mean(visiteds):.2f}", f"{mean(lengths):.2f}", repeats])
|
||||||
|
return summary_rows, raw_rows
|
||||||
|
|
||||||
|
|
||||||
|
def save_csv(path, rows):
|
||||||
|
with open(path, "w", newline="", encoding="utf-8") as f:
|
||||||
|
csv.writer(f).writerows(rows)
|
||||||
|
|
||||||
|
|
||||||
|
def plot_summary(summary_rows):
|
||||||
|
by_maze = {}
|
||||||
|
for row in summary_rows[1:]:
|
||||||
|
maze_name, strategy, avg_time, avg_visited, avg_len, runs = row
|
||||||
|
by_maze.setdefault(maze_name, []).append((strategy, float(avg_time), float(avg_visited), float(avg_len)))
|
||||||
|
|
||||||
|
for maze_name, items in by_maze.items():
|
||||||
|
items.sort(key=lambda t: t[0])
|
||||||
|
strategies = [i[0] for i in items]
|
||||||
|
x = list(range(len(strategies)))
|
||||||
|
|
||||||
|
plt.figure(figsize=(8, 4))
|
||||||
|
plt.bar(x, [i[1] for i in items])
|
||||||
|
plt.xticks(x, strategies)
|
||||||
|
plt.ylabel("ms")
|
||||||
|
plt.title(f"{maze_name} — avg time")
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig(OUT_DIR / f"{maze_name}_time.png", dpi=150)
|
||||||
|
plt.close()
|
||||||
|
|
||||||
|
plt.figure(figsize=(8, 4))
|
||||||
|
plt.bar(x, [i[2] for i in items])
|
||||||
|
plt.xticks(x, strategies)
|
||||||
|
plt.ylabel("cells")
|
||||||
|
plt.title(f"{maze_name} — visited cells")
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig(OUT_DIR / f"{maze_name}_visited.png", dpi=150)
|
||||||
|
plt.close()
|
||||||
|
|
||||||
|
plt.figure(figsize=(8, 4))
|
||||||
|
plt.bar(x, [i[3] for i in items])
|
||||||
|
plt.xticks(x, strategies)
|
||||||
|
plt.ylabel("cells")
|
||||||
|
plt.title(f"{maze_name} — path length")
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig(OUT_DIR / f"{maze_name}_length.png", dpi=150)
|
||||||
|
plt.close()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
OUT_DIR.mkdir(exist_ok=True)
|
||||||
|
|
||||||
|
strategies = [
|
||||||
|
("BFS", BFSStrategy),
|
||||||
|
("DFS", DFSStrategy),
|
||||||
|
("A*", AStarStrategy),
|
||||||
|
("Dijkstra", DijkstraStrategy),
|
||||||
|
]
|
||||||
|
|
||||||
|
mazes = [
|
||||||
|
("small_10x10", generate_simple_maze(10, 10)),
|
||||||
|
("medium_50x50", generate_branching_maze(50, 50)),
|
||||||
|
("large_100x100", generate_branching_maze(100, 100, seed=99, wall_density=0.35)),
|
||||||
|
("empty_30x30", generate_empty_maze(30, 30)),
|
||||||
|
("no_path_30x30", generate_no_path_maze(30, 30)),
|
||||||
|
("weighted_30x30", generate_weighted_maze(30, 30)),
|
||||||
|
]
|
||||||
|
|
||||||
|
summary = [["maze", "strategy", "avg_time_ms", "avg_visited_cells", "avg_path_length", "runs"]]
|
||||||
|
raw = [["maze", "strategy", "run", "time_ms", "visited_cells", "path_length"]]
|
||||||
|
|
||||||
|
for maze_name, maze in mazes:
|
||||||
|
s_rows, r_rows = bench_one_maze(maze_name, maze, strategies, repeats=5)
|
||||||
|
summary.extend(s_rows)
|
||||||
|
raw.extend(r_rows)
|
||||||
|
|
||||||
|
save_csv(OUT_DIR / "summary.csv", summary)
|
||||||
|
save_csv(OUT_DIR / "raw.csv", raw)
|
||||||
|
plot_summary(summary)
|
||||||
|
|
||||||
|
print("Saved to", OUT_DIR.resolve())
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
BIN
BolonkinNM/experiment_results/empty_30x30_length.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
BolonkinNM/experiment_results/empty_30x30_time.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
BolonkinNM/experiment_results/empty_30x30_visited.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
BolonkinNM/experiment_results/large_100x100_length.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
BolonkinNM/experiment_results/large_100x100_time.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
BolonkinNM/experiment_results/large_100x100_visited.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
BolonkinNM/experiment_results/medium_50x50_length.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
BolonkinNM/experiment_results/medium_50x50_time.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
BolonkinNM/experiment_results/medium_50x50_visited.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
BolonkinNM/experiment_results/no_path_30x30_length.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
BolonkinNM/experiment_results/no_path_30x30_time.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
BolonkinNM/experiment_results/no_path_30x30_visited.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
121
BolonkinNM/experiment_results/raw.csv
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
maze,strategy,run,time_ms,visited_cells,path_length
|
||||||
|
small_10x10,BFS,1,0.044300,15,15
|
||||||
|
small_10x10,BFS,2,0.022800,15,15
|
||||||
|
small_10x10,BFS,3,0.020400,15,15
|
||||||
|
small_10x10,BFS,4,0.020300,15,15
|
||||||
|
small_10x10,BFS,5,0.018700,15,15
|
||||||
|
small_10x10,DFS,1,0.031200,15,15
|
||||||
|
small_10x10,DFS,2,0.022000,15,15
|
||||||
|
small_10x10,DFS,3,0.021200,15,15
|
||||||
|
small_10x10,DFS,4,0.020800,15,15
|
||||||
|
small_10x10,DFS,5,0.020500,15,15
|
||||||
|
small_10x10,A*,1,0.048900,15,15
|
||||||
|
small_10x10,A*,2,0.034700,15,15
|
||||||
|
small_10x10,A*,3,0.029400,15,15
|
||||||
|
small_10x10,A*,4,0.029100,15,15
|
||||||
|
small_10x10,A*,5,0.029300,15,15
|
||||||
|
small_10x10,Dijkstra,1,0.037900,15,15
|
||||||
|
small_10x10,Dijkstra,2,0.028500,15,15
|
||||||
|
small_10x10,Dijkstra,3,0.026800,15,15
|
||||||
|
small_10x10,Dijkstra,4,0.026400,15,15
|
||||||
|
small_10x10,Dijkstra,5,0.026700,15,15
|
||||||
|
medium_50x50,BFS,1,2.105800,1579,95
|
||||||
|
medium_50x50,BFS,2,1.928700,1579,95
|
||||||
|
medium_50x50,BFS,3,1.969500,1579,95
|
||||||
|
medium_50x50,BFS,4,1.938800,1579,95
|
||||||
|
medium_50x50,BFS,5,1.943600,1579,95
|
||||||
|
medium_50x50,DFS,1,1.927300,1277,647
|
||||||
|
medium_50x50,DFS,2,1.856300,1277,647
|
||||||
|
medium_50x50,DFS,3,1.890100,1277,647
|
||||||
|
medium_50x50,DFS,4,1.868000,1277,647
|
||||||
|
medium_50x50,DFS,5,1.865500,1277,647
|
||||||
|
medium_50x50,A*,1,2.359000,927,95
|
||||||
|
medium_50x50,A*,2,2.193700,927,95
|
||||||
|
medium_50x50,A*,3,2.178400,927,95
|
||||||
|
medium_50x50,A*,4,2.181800,927,95
|
||||||
|
medium_50x50,A*,5,2.174500,927,95
|
||||||
|
medium_50x50,Dijkstra,1,3.534700,1579,95
|
||||||
|
medium_50x50,Dijkstra,2,3.435500,1579,95
|
||||||
|
medium_50x50,Dijkstra,3,3.457600,1579,95
|
||||||
|
medium_50x50,Dijkstra,4,3.417300,1579,95
|
||||||
|
medium_50x50,Dijkstra,5,3.538000,1579,95
|
||||||
|
large_100x100,BFS,1,8.624100,5566,195
|
||||||
|
large_100x100,BFS,2,7.706900,5566,195
|
||||||
|
large_100x100,BFS,3,9.723300,5566,195
|
||||||
|
large_100x100,BFS,4,7.585700,5566,195
|
||||||
|
large_100x100,BFS,5,8.031300,5566,195
|
||||||
|
large_100x100,DFS,1,5.512400,3543,1531
|
||||||
|
large_100x100,DFS,2,5.329300,3543,1531
|
||||||
|
large_100x100,DFS,3,5.223300,3543,1531
|
||||||
|
large_100x100,DFS,4,5.729900,3543,1531
|
||||||
|
large_100x100,DFS,5,5.497400,3543,1531
|
||||||
|
large_100x100,A*,1,2.101500,853,195
|
||||||
|
large_100x100,A*,2,2.264500,853,195
|
||||||
|
large_100x100,A*,3,2.064100,853,195
|
||||||
|
large_100x100,A*,4,2.031700,853,195
|
||||||
|
large_100x100,A*,5,2.046500,853,195
|
||||||
|
large_100x100,Dijkstra,1,25.021300,5571,195
|
||||||
|
large_100x100,Dijkstra,2,13.541100,5571,195
|
||||||
|
large_100x100,Dijkstra,3,12.884100,5571,195
|
||||||
|
large_100x100,Dijkstra,4,13.481800,5571,195
|
||||||
|
large_100x100,Dijkstra,5,12.748000,5571,195
|
||||||
|
empty_30x30,BFS,1,1.234300,896,55
|
||||||
|
empty_30x30,BFS,2,1.163400,896,55
|
||||||
|
empty_30x30,BFS,3,1.145700,896,55
|
||||||
|
empty_30x30,BFS,4,1.177300,896,55
|
||||||
|
empty_30x30,BFS,5,1.175100,896,55
|
||||||
|
empty_30x30,DFS,1,1.338000,842,815
|
||||||
|
empty_30x30,DFS,2,1.296500,842,815
|
||||||
|
empty_30x30,DFS,3,1.296700,842,815
|
||||||
|
empty_30x30,DFS,4,1.280100,842,815
|
||||||
|
empty_30x30,DFS,5,1.290800,842,815
|
||||||
|
empty_30x30,A*,1,2.183400,784,55
|
||||||
|
empty_30x30,A*,2,2.522900,784,55
|
||||||
|
empty_30x30,A*,3,1.985000,784,55
|
||||||
|
empty_30x30,A*,4,1.972100,784,55
|
||||||
|
empty_30x30,A*,5,2.088600,784,55
|
||||||
|
empty_30x30,Dijkstra,1,2.080400,896,55
|
||||||
|
empty_30x30,Dijkstra,2,2.100100,896,55
|
||||||
|
empty_30x30,Dijkstra,3,2.130700,896,55
|
||||||
|
empty_30x30,Dijkstra,4,2.073600,896,55
|
||||||
|
empty_30x30,Dijkstra,5,2.095900,896,55
|
||||||
|
no_path_30x30,BFS,1,0.645900,450,0
|
||||||
|
no_path_30x30,BFS,2,0.566600,450,0
|
||||||
|
no_path_30x30,BFS,3,0.566000,450,0
|
||||||
|
no_path_30x30,BFS,4,0.583500,450,0
|
||||||
|
no_path_30x30,BFS,5,0.568900,450,0
|
||||||
|
no_path_30x30,DFS,1,0.692100,450,0
|
||||||
|
no_path_30x30,DFS,2,0.676900,450,0
|
||||||
|
no_path_30x30,DFS,3,0.703500,450,0
|
||||||
|
no_path_30x30,DFS,4,0.722300,450,0
|
||||||
|
no_path_30x30,DFS,5,0.672000,450,0
|
||||||
|
no_path_30x30,A*,1,1.112700,450,0
|
||||||
|
no_path_30x30,A*,2,1.130000,450,0
|
||||||
|
no_path_30x30,A*,3,1.096100,450,0
|
||||||
|
no_path_30x30,A*,4,1.111400,450,0
|
||||||
|
no_path_30x30,A*,5,1.183500,450,0
|
||||||
|
no_path_30x30,Dijkstra,1,1.023300,450,0
|
||||||
|
no_path_30x30,Dijkstra,2,1.011700,450,0
|
||||||
|
no_path_30x30,Dijkstra,3,1.127200,450,0
|
||||||
|
no_path_30x30,Dijkstra,4,1.110200,450,0
|
||||||
|
no_path_30x30,Dijkstra,5,1.043900,450,0
|
||||||
|
weighted_30x30,BFS,1,1.074700,788,55
|
||||||
|
weighted_30x30,BFS,2,0.997700,788,55
|
||||||
|
weighted_30x30,BFS,3,0.992700,788,55
|
||||||
|
weighted_30x30,BFS,4,1.010800,788,55
|
||||||
|
weighted_30x30,BFS,5,1.035000,788,55
|
||||||
|
weighted_30x30,DFS,1,1.130200,693,479
|
||||||
|
weighted_30x30,DFS,2,1.057400,693,479
|
||||||
|
weighted_30x30,DFS,3,1.049900,693,479
|
||||||
|
weighted_30x30,DFS,4,1.051600,693,479
|
||||||
|
weighted_30x30,DFS,5,1.059100,693,479
|
||||||
|
weighted_30x30,A*,1,0.402200,126,55
|
||||||
|
weighted_30x30,A*,2,0.384100,126,55
|
||||||
|
weighted_30x30,A*,3,0.360000,126,55
|
||||||
|
weighted_30x30,A*,4,0.360700,126,55
|
||||||
|
weighted_30x30,A*,5,0.353500,126,55
|
||||||
|
weighted_30x30,Dijkstra,1,1.834900,781,55
|
||||||
|
weighted_30x30,Dijkstra,2,1.759000,781,55
|
||||||
|
weighted_30x30,Dijkstra,3,1.786300,781,55
|
||||||
|
weighted_30x30,Dijkstra,4,1.740500,781,55
|
||||||
|
weighted_30x30,Dijkstra,5,1.807100,781,55
|
||||||
|
BIN
BolonkinNM/experiment_results/small_10x10_length.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
BolonkinNM/experiment_results/small_10x10_time.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
BolonkinNM/experiment_results/small_10x10_visited.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
25
BolonkinNM/experiment_results/summary.csv
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
maze,strategy,avg_time_ms,avg_visited_cells,avg_path_length,runs
|
||||||
|
small_10x10,BFS,0.025300,15.00,15.00,5
|
||||||
|
small_10x10,DFS,0.023140,15.00,15.00,5
|
||||||
|
small_10x10,A*,0.034280,15.00,15.00,5
|
||||||
|
small_10x10,Dijkstra,0.029260,15.00,15.00,5
|
||||||
|
medium_50x50,BFS,1.977280,1579.00,95.00,5
|
||||||
|
medium_50x50,DFS,1.881440,1277.00,647.00,5
|
||||||
|
medium_50x50,A*,2.217480,927.00,95.00,5
|
||||||
|
medium_50x50,Dijkstra,3.476620,1579.00,95.00,5
|
||||||
|
large_100x100,BFS,8.334260,5566.00,195.00,5
|
||||||
|
large_100x100,DFS,5.458460,3543.00,1531.00,5
|
||||||
|
large_100x100,A*,2.101660,853.00,195.00,5
|
||||||
|
large_100x100,Dijkstra,15.535260,5571.00,195.00,5
|
||||||
|
empty_30x30,BFS,1.179160,896.00,55.00,5
|
||||||
|
empty_30x30,DFS,1.300420,842.00,815.00,5
|
||||||
|
empty_30x30,A*,2.150400,784.00,55.00,5
|
||||||
|
empty_30x30,Dijkstra,2.096140,896.00,55.00,5
|
||||||
|
no_path_30x30,BFS,0.586180,450.00,0.00,5
|
||||||
|
no_path_30x30,DFS,0.693360,450.00,0.00,5
|
||||||
|
no_path_30x30,A*,1.126740,450.00,0.00,5
|
||||||
|
no_path_30x30,Dijkstra,1.063260,450.00,0.00,5
|
||||||
|
weighted_30x30,BFS,1.022180,788.00,55.00,5
|
||||||
|
weighted_30x30,DFS,1.069640,693.00,479.00,5
|
||||||
|
weighted_30x30,A*,0.372100,126.00,55.00,5
|
||||||
|
weighted_30x30,Dijkstra,1.785560,781.00,55.00,5
|
||||||
|
BIN
BolonkinNM/experiment_results/weighted_30x30_length.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
BolonkinNM/experiment_results/weighted_30x30_time.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
BolonkinNM/experiment_results/weighted_30x30_visited.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
59
BolonkinNM/main.py
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
from builders.text_file_maze_builder import TextFileMazeBuilder
|
||||||
|
from core.player import Player
|
||||||
|
from observer.console_view import ConsoleView
|
||||||
|
from solver.maze_solver import MazeSolver
|
||||||
|
from strategies.astar_strategy import AStarStrategy
|
||||||
|
from strategies.bfs_strategy import BFSStrategy
|
||||||
|
from strategies.dfs_strategy import DFSStrategy
|
||||||
|
from controller.game_controller import GameController
|
||||||
|
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
BASE_DIR = Path(__file__).resolve().parent
|
||||||
|
|
||||||
|
|
||||||
|
def run_demo():
|
||||||
|
builder = TextFileMazeBuilder()
|
||||||
|
maze = builder.buildFromFile(str(BASE_DIR / "mazes" / "maze_small.txt"))
|
||||||
|
|
||||||
|
view = ConsoleView()
|
||||||
|
view.update({"type": "maze_loaded", "message": "Maze loaded"})
|
||||||
|
view.render(maze)
|
||||||
|
|
||||||
|
solver = MazeSolver(maze)
|
||||||
|
solver.addObserver(view)
|
||||||
|
|
||||||
|
for strategy in (BFSStrategy(), DFSStrategy(), AStarStrategy()):
|
||||||
|
solver.setStrategy(strategy)
|
||||||
|
stats = solver.solve()
|
||||||
|
|
||||||
|
print()
|
||||||
|
print(f"=== {strategy.name} ===")
|
||||||
|
print(f"Time: {stats.timeMs:.3f} ms")
|
||||||
|
print(f"Visited cells: {stats.visitedCells}")
|
||||||
|
print(f"Path length: {stats.pathLength}")
|
||||||
|
print(f"Path found: {'yes' if stats.found else 'no'}")
|
||||||
|
|
||||||
|
view.render(maze, path=stats.path)
|
||||||
|
|
||||||
|
player = Player(maze.startCell)
|
||||||
|
controller = GameController(maze, player, view)
|
||||||
|
|
||||||
|
print("Manual mode: W/A/S/D move, Z undo, Q quit")
|
||||||
|
view.render(maze, player_position=player.currentCell)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
cmd = input("Command: ").strip().upper()
|
||||||
|
if cmd == "Q":
|
||||||
|
break
|
||||||
|
if cmd == "Z":
|
||||||
|
controller.undo()
|
||||||
|
elif cmd in {"W", "A", "S", "D"}:
|
||||||
|
controller.move(cmd)
|
||||||
|
else:
|
||||||
|
print("Unknown command")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
run_demo()
|
||||||
9
BolonkinNM/mazes/maze_empty.txt
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
S
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
E
|
||||||
11
BolonkinNM/mazes/maze_large.txt
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
####################################################################################################
|
||||||
|
#S # # # # # # # # # # # # # # # E#
|
||||||
|
# # ### ### # ###### # ### # ## # #### # ####### # #### # # ### ## # ## # # ## # ## # ##### ### ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
# ##### # ######## # ### # ## # #### # ####### ## ### # # #### ####### ## ####### ####### # ### ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # #
|
||||||
|
### # # ###### # ########### ########### ### ####### # ####### ### # # ###### # ### ### # ### ####
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
# ### ###### # ##### # ### # ####### # ### ### ## # ###### # ### # ### ###### # ### # ### ### ## #
|
||||||
|
# # # # # # # # #
|
||||||
|
####################################################################################################
|
||||||
11
BolonkinNM/mazes/maze_medium.txt
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
##################################################
|
||||||
|
#S # # # # # # E#
|
||||||
|
# # ### ### # ###### # ### # ## # #### # ####### ##
|
||||||
|
# # # # # # # # # # # # # #
|
||||||
|
# ##### # ######## # ### # ## # #### # ####### ## #
|
||||||
|
# # # # # # # # # #
|
||||||
|
### # # ###### # ########### ########### ### ######
|
||||||
|
# # # # # # # # # # #
|
||||||
|
# ### ###### # ##### # ### # ####### # ### ### ## #
|
||||||
|
# # # # #
|
||||||
|
##################################################
|
||||||
9
BolonkinNM/mazes/maze_no_path.txt
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
##########
|
||||||
|
#S #
|
||||||
|
# ###### #
|
||||||
|
# # #
|
||||||
|
##########
|
||||||
|
# #E#
|
||||||
|
# ###### #
|
||||||
|
# #
|
||||||
|
##########
|
||||||
7
BolonkinNM/mazes/maze_small.txt
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
##########
|
||||||
|
#S #E#
|
||||||
|
# ## # # ##
|
||||||
|
# # #
|
||||||
|
# #### # #
|
||||||
|
# # #
|
||||||
|
##########
|
||||||
10
BolonkinNM/mazes/maze_weighted.txt
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
1111111111111111111111111111
|
||||||
|
1S11111111111111111111111111
|
||||||
|
1111111111111111111111111111
|
||||||
|
1111111111111111111111111111
|
||||||
|
1111111111111222222222222111
|
||||||
|
1111111111111222222222222111
|
||||||
|
1111111111111333333333333111
|
||||||
|
1111111111111333333333333111
|
||||||
|
111111111111111111111111111E
|
||||||
|
1111111111111111111111111111
|
||||||
0
BolonkinNM/observer/__init__.py
Normal file
26
BolonkinNM/observer/console_view.py
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
import os
|
||||||
|
from observer.observer import Observer
|
||||||
|
|
||||||
|
|
||||||
|
class ConsoleView(Observer):
|
||||||
|
def update(self, event):
|
||||||
|
if isinstance(event, str):
|
||||||
|
print(f"[EVENT] {event}")
|
||||||
|
elif isinstance(event, dict):
|
||||||
|
event_type = event.get("type", "unknown")
|
||||||
|
if event_type == "search_finished":
|
||||||
|
stats = event.get("stats")
|
||||||
|
print(f"[EVENT] search finished: {stats}")
|
||||||
|
else:
|
||||||
|
print(f"[EVENT] {event_type}: {event}")
|
||||||
|
else:
|
||||||
|
print("[EVENT] unknown")
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
os.system("cls" if os.name == "nt" else "clear")
|
||||||
|
|
||||||
|
def render(self, maze, player_position=None, path=None, clear_screen=False):
|
||||||
|
if clear_screen:
|
||||||
|
self.clear()
|
||||||
|
print(maze.render(player_position=player_position, path=path))
|
||||||
|
print()
|
||||||
7
BolonkinNM/observer/observer.py
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
|
||||||
|
|
||||||
|
class Observer(ABC):
|
||||||
|
@abstractmethod
|
||||||
|
def update(self, event):
|
||||||
|
raise NotImplementedError
|
||||||
1
BolonkinNM/requirements.txt
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
matplotlib
|
||||||
0
BolonkinNM/solver/__init__.py
Normal file
50
BolonkinNM/solver/maze_solver.py
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
import time
|
||||||
|
from core.search_stats import SearchStats
|
||||||
|
|
||||||
|
|
||||||
|
class MazeSolver:
|
||||||
|
def __init__(self, maze, strategy=None):
|
||||||
|
self.maze = maze
|
||||||
|
self.strategy = strategy
|
||||||
|
self.observers = []
|
||||||
|
|
||||||
|
def setStrategy(self, strategy):
|
||||||
|
self.strategy = strategy
|
||||||
|
|
||||||
|
def addObserver(self, observer):
|
||||||
|
if observer not in self.observers:
|
||||||
|
self.observers.append(observer)
|
||||||
|
|
||||||
|
def removeObserver(self, observer):
|
||||||
|
if observer in self.observers:
|
||||||
|
self.observers.remove(observer)
|
||||||
|
|
||||||
|
def notify(self, event):
|
||||||
|
for observer in self.observers:
|
||||||
|
observer.update(event)
|
||||||
|
|
||||||
|
def solve(self):
|
||||||
|
if self.strategy is None:
|
||||||
|
raise ValueError("Strategy is not set")
|
||||||
|
self.notify({"type": "search_started", "strategy": self.strategy.name})
|
||||||
|
|
||||||
|
start_time = time.perf_counter()
|
||||||
|
path = self.strategy.findPath(self.maze, self.maze.startCell, self.maze.exitCell)
|
||||||
|
end_time = time.perf_counter()
|
||||||
|
|
||||||
|
stats = SearchStats(
|
||||||
|
timeMs=(end_time - start_time) * 1000.0,
|
||||||
|
visitedCells=getattr(self.strategy, "visitedCount", 0),
|
||||||
|
pathLength=len(path),
|
||||||
|
path=path,
|
||||||
|
found=bool(path),
|
||||||
|
algorithm=getattr(self.strategy, "name", "")
|
||||||
|
)
|
||||||
|
|
||||||
|
if stats.found:
|
||||||
|
self.notify({"type": "path_found", "strategy": stats.algorithm, "length": stats.pathLength})
|
||||||
|
else:
|
||||||
|
self.notify({"type": "path_not_found", "strategy": stats.algorithm})
|
||||||
|
|
||||||
|
self.notify({"type": "search_finished", "stats": stats})
|
||||||
|
return stats
|
||||||
0
BolonkinNM/strategies/__init__.py
Normal file
45
BolonkinNM/strategies/astar_strategy.py
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
import heapq
|
||||||
|
from strategies.pathfinding_strategy import PathFindingStrategy
|
||||||
|
|
||||||
|
|
||||||
|
class AStarStrategy(PathFindingStrategy):
|
||||||
|
name = "A*"
|
||||||
|
|
||||||
|
def heuristic(self, cell, exitCell):
|
||||||
|
return abs(cell.x - exitCell.x) + abs(cell.y - exitCell.y)
|
||||||
|
|
||||||
|
def findPath(self, maze, start, exitCell):
|
||||||
|
self.visitedCount = 0
|
||||||
|
if start is None or exitCell is None:
|
||||||
|
return []
|
||||||
|
|
||||||
|
open_set = []
|
||||||
|
heapq.heappush(open_set, (0, 0, start.x, start.y, start))
|
||||||
|
parent = {}
|
||||||
|
g_score = {(start.x, start.y): 0}
|
||||||
|
closed = set()
|
||||||
|
|
||||||
|
while open_set:
|
||||||
|
f_score, current_g, _, _, current = heapq.heappop(open_set)
|
||||||
|
pos = (current.x, current.y)
|
||||||
|
|
||||||
|
if pos in closed:
|
||||||
|
continue
|
||||||
|
|
||||||
|
closed.add(pos)
|
||||||
|
self.visitedCount += 1
|
||||||
|
|
||||||
|
if current.x == exitCell.x and current.y == exitCell.y:
|
||||||
|
return self._restore_path(parent, start, exitCell)
|
||||||
|
|
||||||
|
for neighbor in maze.getNeighbors(current):
|
||||||
|
npos = (neighbor.x, neighbor.y)
|
||||||
|
tentative_g = current_g + getattr(neighbor, "weight", 1)
|
||||||
|
|
||||||
|
if tentative_g < g_score.get(npos, float("inf")):
|
||||||
|
g_score[npos] = tentative_g
|
||||||
|
parent[npos] = current
|
||||||
|
new_f = tentative_g + self.heuristic(neighbor, exitCell)
|
||||||
|
heapq.heappush(open_set, (new_f, tentative_g, neighbor.x, neighbor.y, neighbor))
|
||||||
|
|
||||||
|
return []
|
||||||
31
BolonkinNM/strategies/bfs_strategy.py
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
from collections import deque
|
||||||
|
from strategies.pathfinding_strategy import PathFindingStrategy
|
||||||
|
|
||||||
|
|
||||||
|
class BFSStrategy(PathFindingStrategy):
|
||||||
|
name = "BFS"
|
||||||
|
|
||||||
|
def findPath(self, maze, start, exitCell):
|
||||||
|
self.visitedCount = 0
|
||||||
|
if start is None or exitCell is None:
|
||||||
|
return []
|
||||||
|
|
||||||
|
queue = deque([start])
|
||||||
|
visited = {(start.x, start.y)}
|
||||||
|
parent = {}
|
||||||
|
|
||||||
|
while queue:
|
||||||
|
current = queue.popleft()
|
||||||
|
self.visitedCount += 1
|
||||||
|
|
||||||
|
if current.x == exitCell.x and current.y == exitCell.y:
|
||||||
|
return self._restore_path(parent, start, exitCell)
|
||||||
|
|
||||||
|
for neighbor in maze.getNeighbors(current):
|
||||||
|
pos = (neighbor.x, neighbor.y)
|
||||||
|
if pos not in visited:
|
||||||
|
visited.add(pos)
|
||||||
|
parent[pos] = current
|
||||||
|
queue.append(neighbor)
|
||||||
|
|
||||||
|
return []
|
||||||
35
BolonkinNM/strategies/dfs_strategy.py
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
from strategies.pathfinding_strategy import PathFindingStrategy
|
||||||
|
|
||||||
|
|
||||||
|
class DFSStrategy(PathFindingStrategy):
|
||||||
|
name = "DFS"
|
||||||
|
|
||||||
|
def findPath(self, maze, start, exitCell):
|
||||||
|
self.visitedCount = 0
|
||||||
|
if start is None or exitCell is None:
|
||||||
|
return []
|
||||||
|
|
||||||
|
stack = [start]
|
||||||
|
visited = set()
|
||||||
|
parent = {}
|
||||||
|
|
||||||
|
while stack:
|
||||||
|
current = stack.pop()
|
||||||
|
pos = (current.x, current.y)
|
||||||
|
if pos in visited:
|
||||||
|
continue
|
||||||
|
|
||||||
|
visited.add(pos)
|
||||||
|
self.visitedCount += 1
|
||||||
|
|
||||||
|
if current.x == exitCell.x and current.y == exitCell.y:
|
||||||
|
return self._restore_path(parent, start, exitCell)
|
||||||
|
|
||||||
|
neighbors = maze.getNeighbors(current)
|
||||||
|
for neighbor in reversed(neighbors):
|
||||||
|
npos = (neighbor.x, neighbor.y)
|
||||||
|
if npos not in visited:
|
||||||
|
parent[npos] = current
|
||||||
|
stack.append(neighbor)
|
||||||
|
|
||||||
|
return []
|
||||||
41
BolonkinNM/strategies/dijkstra_strategy.py
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
import heapq
|
||||||
|
from strategies.pathfinding_strategy import PathFindingStrategy
|
||||||
|
|
||||||
|
|
||||||
|
class DijkstraStrategy(PathFindingStrategy):
|
||||||
|
name = "Dijkstra"
|
||||||
|
|
||||||
|
def findPath(self, maze, start, exitCell):
|
||||||
|
self.visitedCount = 0
|
||||||
|
if start is None or exitCell is None:
|
||||||
|
return []
|
||||||
|
|
||||||
|
pq = [(0, start.x, start.y, start)]
|
||||||
|
dist = {(start.x, start.y): 0}
|
||||||
|
parent = {}
|
||||||
|
closed = set()
|
||||||
|
|
||||||
|
while pq:
|
||||||
|
current_cost, _, _, current = heapq.heappop(pq)
|
||||||
|
pos = (current.x, current.y)
|
||||||
|
|
||||||
|
if pos in closed:
|
||||||
|
continue
|
||||||
|
|
||||||
|
closed.add(pos)
|
||||||
|
self.visitedCount += 1
|
||||||
|
|
||||||
|
if current.x == exitCell.x and current.y == exitCell.y:
|
||||||
|
return self._restore_path(parent, start, exitCell)
|
||||||
|
|
||||||
|
for neighbor in maze.getNeighbors(current):
|
||||||
|
npos = (neighbor.x, neighbor.y)
|
||||||
|
step_cost = getattr(neighbor, "weight", 1)
|
||||||
|
new_cost = current_cost + step_cost
|
||||||
|
|
||||||
|
if new_cost < dist.get(npos, float("inf")):
|
||||||
|
dist[npos] = new_cost
|
||||||
|
parent[npos] = current
|
||||||
|
heapq.heappush(pq, (new_cost, neighbor.x, neighbor.y, neighbor))
|
||||||
|
|
||||||
|
return []
|
||||||
30
BolonkinNM/strategies/pathfinding_strategy.py
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
|
||||||
|
|
||||||
|
class PathFindingStrategy(ABC):
|
||||||
|
name = "Base"
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.visitedCount = 0
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def findPath(self, maze, start, exitCell):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def _restore_path(self, parent, start, exitCell):
|
||||||
|
if exitCell is None or start is None:
|
||||||
|
return []
|
||||||
|
|
||||||
|
path = []
|
||||||
|
current = exitCell
|
||||||
|
|
||||||
|
while True:
|
||||||
|
path.append(current)
|
||||||
|
if current.x == start.x and current.y == start.y:
|
||||||
|
break
|
||||||
|
current = parent.get((current.x, current.y))
|
||||||
|
if current is None:
|
||||||
|
return []
|
||||||
|
|
||||||
|
path.reverse()
|
||||||
|
return path
|
||||||
1
BoriskovaDV/428.md
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
|
||||||
71
BoriskovaDV/docs/data/1-st-exercise/bst_phonebook.py
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
def create_node(name, phone):
|
||||||
|
return {'name': name, 'phone': phone, 'left': None, 'right': None}
|
||||||
|
|
||||||
|
def bst_insert(root, name, phone):
|
||||||
|
if root is None:
|
||||||
|
return create_node(name, phone)
|
||||||
|
|
||||||
|
if name == root['name']:
|
||||||
|
root['phone'] = phone
|
||||||
|
elif name < root['name']:
|
||||||
|
root['left'] = bst_insert(root['left'], name, phone)
|
||||||
|
else:
|
||||||
|
root['right'] = bst_insert(root['right'], name, phone)
|
||||||
|
return root
|
||||||
|
|
||||||
|
def bst_find(root, name):
|
||||||
|
if root is None:
|
||||||
|
return None
|
||||||
|
if name == root['name']:
|
||||||
|
return root['phone']
|
||||||
|
elif name < root['name']:
|
||||||
|
return bst_find(root['left'], name)
|
||||||
|
else:
|
||||||
|
return bst_find(root['right'], name)
|
||||||
|
|
||||||
|
def _find_min(node):
|
||||||
|
while node['left'] is not None:
|
||||||
|
node = node['left']
|
||||||
|
return node
|
||||||
|
|
||||||
|
def bst_delete(root, name):
|
||||||
|
if root is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if name < root['name']:
|
||||||
|
root['left'] = bst_delete(root['left'], name)
|
||||||
|
elif name > root['name']:
|
||||||
|
root['right'] = bst_delete(root['right'], name)
|
||||||
|
else:
|
||||||
|
if root['left'] is None:
|
||||||
|
return root['right']
|
||||||
|
if root['right'] is None:
|
||||||
|
return root['left']
|
||||||
|
min_node = _find_min(root['right'])
|
||||||
|
root['name'] = min_node['name']
|
||||||
|
root['phone'] = min_node['phone']
|
||||||
|
root['right'] = bst_delete(root['right'], min_node['name'])
|
||||||
|
return root
|
||||||
|
|
||||||
|
def bst_list_all(root):
|
||||||
|
result = []
|
||||||
|
def inorder(node):
|
||||||
|
if node is None:
|
||||||
|
return
|
||||||
|
inorder(node['left'])
|
||||||
|
result.append((node['name'], node['phone']))
|
||||||
|
inorder(node['right'])
|
||||||
|
inorder(root)
|
||||||
|
return result
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
root = None
|
||||||
|
root = bst_insert(root, 'Иван', '123-456')
|
||||||
|
root = bst_insert(root, 'Борис', '789-012')
|
||||||
|
root = bst_insert(root, 'Анна', '345-678')
|
||||||
|
root = bst_insert(root, 'Иван', '111-222')
|
||||||
|
print(bst_list_all(root))
|
||||||
|
print(bst_find(root, 'Иван'))
|
||||||
|
print(bst_find(root, 'Петр'))
|
||||||
|
root = bst_delete(root, 'Борис')
|
||||||
|
print(bst_list_all(root))
|
||||||
126
BoriskovaDV/docs/data/1-st-exercise/experiment.py
Normal file
|
|
@ -0,0 +1,126 @@
|
||||||
|
import random
|
||||||
|
import time
|
||||||
|
import csv
|
||||||
|
import sys
|
||||||
|
sys.setrecursionlimit(20000)
|
||||||
|
|
||||||
|
from linked_list_phonebook import ll_insert, ll_find, ll_delete, ll_list_all
|
||||||
|
from hash_table_phonebook import ht_insert, ht_find, ht_delete, ht_list_all
|
||||||
|
from bst_phonebook import bst_insert, bst_find, bst_delete, bst_list_all
|
||||||
|
|
||||||
|
def generate_records(n, seed=42):
|
||||||
|
random.seed(seed)
|
||||||
|
records = []
|
||||||
|
for i in range(1, n+1):
|
||||||
|
name = f"User_{i:05d}"
|
||||||
|
phone = f"{random.randint(100,999)}-{random.randint(1000,9999)}"
|
||||||
|
records.append((name, phone))
|
||||||
|
return records
|
||||||
|
|
||||||
|
def prepare_datasets(base_records):
|
||||||
|
shuffled = base_records.copy()
|
||||||
|
random.shuffle(shuffled)
|
||||||
|
sorted_records = sorted(base_records, key=lambda x: x[0])
|
||||||
|
return shuffled, sorted_records
|
||||||
|
|
||||||
|
def run_experiment(struct_funcs, records, mode_name, repeats=5):
|
||||||
|
results = []
|
||||||
|
for rep in range(repeats):
|
||||||
|
struct = struct_funcs['create']()
|
||||||
|
|
||||||
|
start = time.perf_counter()
|
||||||
|
for name, phone in records:
|
||||||
|
struct = struct_funcs['insert'](struct, name, phone)
|
||||||
|
end = time.perf_counter()
|
||||||
|
insert_time = end - start
|
||||||
|
|
||||||
|
existing_names = [name for name, _ in records]
|
||||||
|
sample_existing = random.sample(existing_names, 100)
|
||||||
|
nonexistent = [f"NotExist_{i}" for i in range(10)]
|
||||||
|
search_names = sample_existing + nonexistent
|
||||||
|
random.shuffle(search_names)
|
||||||
|
|
||||||
|
start = time.perf_counter()
|
||||||
|
for name in search_names:
|
||||||
|
_ = struct_funcs['find'](struct, name)
|
||||||
|
end = time.perf_counter()
|
||||||
|
find_time = end - start
|
||||||
|
|
||||||
|
to_delete = random.sample(existing_names, 50)
|
||||||
|
start = time.perf_counter()
|
||||||
|
for name in to_delete:
|
||||||
|
struct = struct_funcs['delete'](struct, name)
|
||||||
|
end = time.perf_counter()
|
||||||
|
delete_time = end - start
|
||||||
|
|
||||||
|
results.append({
|
||||||
|
'structure': struct_funcs['name'],
|
||||||
|
'mode': mode_name,
|
||||||
|
'repetition': rep+1,
|
||||||
|
'insert_time': insert_time,
|
||||||
|
'find_time': find_time,
|
||||||
|
'delete_time': delete_time
|
||||||
|
})
|
||||||
|
return results
|
||||||
|
|
||||||
|
def main():
|
||||||
|
N = 10000
|
||||||
|
base_records = generate_records(N)
|
||||||
|
shuffled, sorted_records = prepare_datasets(base_records)
|
||||||
|
|
||||||
|
structures = {
|
||||||
|
'LinkedList': {
|
||||||
|
'name': 'LinkedList',
|
||||||
|
'create': lambda: None,
|
||||||
|
'insert': ll_insert,
|
||||||
|
'find': ll_find,
|
||||||
|
'delete': ll_delete,
|
||||||
|
'list_all': ll_list_all
|
||||||
|
},
|
||||||
|
'HashTable': {
|
||||||
|
'name': 'HashTable',
|
||||||
|
'create': lambda: [None] * 10,
|
||||||
|
'insert': ht_insert,
|
||||||
|
'find': ht_find,
|
||||||
|
'delete': ht_delete,
|
||||||
|
'list_all': ht_list_all
|
||||||
|
},
|
||||||
|
'BST': {
|
||||||
|
'name': 'BST',
|
||||||
|
'create': lambda: None,
|
||||||
|
'insert': bst_insert,
|
||||||
|
'find': bst_find,
|
||||||
|
'delete': bst_delete,
|
||||||
|
'list_all': bst_list_all
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
all_results = []
|
||||||
|
repeats = 5
|
||||||
|
|
||||||
|
for struct_name, funcs in structures.items():
|
||||||
|
print(f"Testing {struct_name} on random order...")
|
||||||
|
res_random = run_experiment(funcs, shuffled, 'random', repeats)
|
||||||
|
all_results.extend(res_random)
|
||||||
|
|
||||||
|
print(f"Testing {struct_name} on sorted order...")
|
||||||
|
res_sorted = run_experiment(funcs, sorted_records, 'sorted', repeats)
|
||||||
|
all_results.extend(res_sorted)
|
||||||
|
|
||||||
|
with open('experiment_results.csv', 'w', newline='', encoding='utf-8') as f:
|
||||||
|
writer = csv.writer(f)
|
||||||
|
writer.writerow(['Structure', 'Mode', 'Repeat', 'Insert (sec)', 'Search (sec)', 'Delete (sec)'])
|
||||||
|
for r in all_results:
|
||||||
|
writer.writerow([
|
||||||
|
r['structure'],
|
||||||
|
r['mode'],
|
||||||
|
r['repetition'],
|
||||||
|
f"{r['insert_time']:.6f}",
|
||||||
|
f"{r['find_time']:.6f}",
|
||||||
|
f"{r['delete_time']:.6f}"
|
||||||
|
])
|
||||||
|
|
||||||
|
print("Experiment finished. Results saved to experiment_results.csv")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
31
BoriskovaDV/docs/data/1-st-exercise/experiment_results.csv
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
Structure,Mode,Repeat,Insert (sec),Search (sec),Delete (sec)
|
||||||
|
LinkedList,random,1,4.432559,0.034196,0.014270
|
||||||
|
LinkedList,random,2,4.999931,0.038043,0.020281
|
||||||
|
LinkedList,random,3,4.771456,0.030191,0.014131
|
||||||
|
LinkedList,random,4,4.707315,0.033500,0.016198
|
||||||
|
LinkedList,random,5,4.721361,0.036586,0.011988
|
||||||
|
LinkedList,sorted,1,4.139028,0.024011,0.010482
|
||||||
|
LinkedList,sorted,2,4.212383,0.024592,0.011765
|
||||||
|
LinkedList,sorted,3,4.674211,0.027756,0.012189
|
||||||
|
LinkedList,sorted,4,4.610210,0.031519,0.012244
|
||||||
|
LinkedList,sorted,5,4.565687,0.029739,0.012747
|
||||||
|
HashTable,random,1,0.659990,0.003889,0.001728
|
||||||
|
HashTable,random,2,0.666055,0.005980,0.002002
|
||||||
|
HashTable,random,3,0.669948,0.004087,0.002176
|
||||||
|
HashTable,random,4,0.661882,0.007439,0.001897
|
||||||
|
HashTable,random,5,0.680420,0.004016,0.001649
|
||||||
|
HashTable,sorted,1,0.648261,0.004277,0.002922
|
||||||
|
HashTable,sorted,2,0.654924,0.004136,0.001793
|
||||||
|
HashTable,sorted,3,0.645509,0.003900,0.002249
|
||||||
|
HashTable,sorted,4,0.637906,0.004056,0.001657
|
||||||
|
HashTable,sorted,5,0.643536,0.003846,0.001741
|
||||||
|
BST,random,1,0.029415,0.000515,0.000183
|
||||||
|
BST,random,2,0.027684,0.000216,0.000142
|
||||||
|
BST,random,3,0.026213,0.000252,0.000159
|
||||||
|
BST,random,4,0.026987,0.000207,0.000135
|
||||||
|
BST,random,5,0.028321,0.000271,0.000183
|
||||||
|
BST,sorted,1,10.293772,0.093178,0.053520
|
||||||
|
BST,sorted,2,10.142204,0.088924,0.049079
|
||||||
|
BST,sorted,3,10.142037,0.078281,0.059416
|
||||||
|
BST,sorted,4,10.139818,0.100162,0.056881
|
||||||
|
BST,sorted,5,10.102982,0.082247,0.051973
|
||||||
|
47
BoriskovaDV/docs/data/1-st-exercise/hash_table_phonebook.py
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
from linked_list_phonebook import ll_insert, ll_find, ll_delete, ll_list_all
|
||||||
|
|
||||||
|
def hash_function(name, table_size):
|
||||||
|
return hash(name) % table_size
|
||||||
|
|
||||||
|
def ht_insert(buckets, name, phone):
|
||||||
|
idx = hash_function(name, len(buckets))
|
||||||
|
head = buckets[idx]
|
||||||
|
new_head = ll_insert(head, name, phone)
|
||||||
|
buckets[idx] = new_head
|
||||||
|
return buckets
|
||||||
|
|
||||||
|
def ht_find(buckets, name):
|
||||||
|
idx = hash_function(name, len(buckets))
|
||||||
|
head = buckets[idx]
|
||||||
|
return ll_find(head, name)
|
||||||
|
|
||||||
|
def ht_delete(buckets, name):
|
||||||
|
idx = hash_function(name, len(buckets))
|
||||||
|
head = buckets[idx]
|
||||||
|
new_head = ll_delete(head, name)
|
||||||
|
buckets[idx] = new_head
|
||||||
|
return buckets
|
||||||
|
|
||||||
|
def ht_list_all(buckets):
|
||||||
|
all_records = []
|
||||||
|
for head in buckets:
|
||||||
|
current = head
|
||||||
|
while current is not None:
|
||||||
|
all_records.append((current['name'], current['phone']))
|
||||||
|
current = current['next']
|
||||||
|
all_records.sort(key=lambda x: x[0])
|
||||||
|
return all_records
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
SIZE = 5
|
||||||
|
buckets = [None] * SIZE
|
||||||
|
|
||||||
|
ht_insert(buckets, 'Иван', '123-456')
|
||||||
|
ht_insert(buckets, 'Борис', '789-012')
|
||||||
|
ht_insert(buckets, 'Анна', '345-678')
|
||||||
|
ht_insert(buckets, 'Иван', '111-222')
|
||||||
|
print(ht_list_all(buckets))
|
||||||
|
print(ht_find(buckets, 'Анна'))
|
||||||
|
print(ht_find(buckets, 'Петр'))
|
||||||
|
ht_delete(buckets, 'Борис')
|
||||||
|
print(ht_list_all(buckets))
|
||||||
67
BoriskovaDV/docs/data/1-st-exercise/linked_list_phonebook.py
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
def create_node(name, phone):
|
||||||
|
return {'name': name, 'phone': phone, 'next': None}
|
||||||
|
|
||||||
|
def ll_insert(head, name, phone):
|
||||||
|
current = head
|
||||||
|
while current is not None:
|
||||||
|
if current['name'] == name:
|
||||||
|
current['phone'] = phone
|
||||||
|
return head
|
||||||
|
current = current['next']
|
||||||
|
|
||||||
|
new_node = create_node(name, phone)
|
||||||
|
|
||||||
|
if head is None:
|
||||||
|
return new_node
|
||||||
|
|
||||||
|
current = head
|
||||||
|
while current['next'] is not None:
|
||||||
|
current = current['next']
|
||||||
|
current['next'] = new_node
|
||||||
|
return head
|
||||||
|
|
||||||
|
def ll_find(head, name):
|
||||||
|
current = head
|
||||||
|
while current is not None:
|
||||||
|
if current['name'] == name:
|
||||||
|
return current['phone']
|
||||||
|
current = current['next']
|
||||||
|
return None
|
||||||
|
|
||||||
|
def ll_delete(head, name):
|
||||||
|
if head is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if head['name'] == name:
|
||||||
|
return head['next']
|
||||||
|
|
||||||
|
prev = head
|
||||||
|
current = head['next']
|
||||||
|
while current is not None:
|
||||||
|
if current['name'] == name:
|
||||||
|
prev['next'] = current['next']
|
||||||
|
return head
|
||||||
|
prev = current
|
||||||
|
current = current['next']
|
||||||
|
return head
|
||||||
|
|
||||||
|
def ll_list_all(head):
|
||||||
|
records = []
|
||||||
|
current = head
|
||||||
|
while current is not None:
|
||||||
|
records.append((current['name'], current['phone']))
|
||||||
|
current = current['next']
|
||||||
|
records.sort(key=lambda pair: pair[0])
|
||||||
|
return records
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
head = None
|
||||||
|
head = ll_insert(head, 'Иван', '123-456')
|
||||||
|
head = ll_insert(head, 'Борис', '789-012')
|
||||||
|
head = ll_insert(head, 'Анна', '345-678')
|
||||||
|
head = ll_insert(head, 'Иван', '111-222')
|
||||||
|
print(ll_list_all(head))
|
||||||
|
print(ll_find(head, 'Иван'))
|
||||||
|
print(ll_find(head, 'Петр'))
|
||||||
|
head = ll_delete(head, 'Борис')
|
||||||
|
print(ll_list_all(head))
|
||||||
BIN
BoriskovaDV/docs/data/1-st-exercise/performance_comparison.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
39
BoriskovaDV/docs/data/1-st-exercise/plot_results.py
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
import pandas as pd
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
df = pd.read_csv('experiment_results.csv')
|
||||||
|
|
||||||
|
mean_times = df.groupby(['Structure', 'Mode'])[['Insert (sec)', 'Search (sec)', 'Delete (sec)']].mean().reset_index()
|
||||||
|
|
||||||
|
structures = mean_times['Structure'].unique()
|
||||||
|
modes = mean_times['Mode'].unique()
|
||||||
|
|
||||||
|
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
|
||||||
|
|
||||||
|
operations = ['Insert (sec)', 'Search (sec)', 'Delete (sec)']
|
||||||
|
titles = ['Insertion', 'Search', 'Deletion']
|
||||||
|
|
||||||
|
for ax, op, title in zip(axes, operations, titles):
|
||||||
|
x = np.arange(len(structures))
|
||||||
|
width = 0.35
|
||||||
|
|
||||||
|
random_vals = []
|
||||||
|
sorted_vals = []
|
||||||
|
for s in structures:
|
||||||
|
random_row = mean_times[(mean_times['Structure'] == s) & (mean_times['Mode'] == 'random')]
|
||||||
|
sorted_row = mean_times[(mean_times['Structure'] == s) & (mean_times['Mode'] == 'sorted')]
|
||||||
|
random_vals.append(random_row[op].values[0] if not random_row.empty else 0)
|
||||||
|
sorted_vals.append(sorted_row[op].values[0] if not sorted_row.empty else 0)
|
||||||
|
|
||||||
|
ax.bar(x - width/2, random_vals, width, label='Random')
|
||||||
|
ax.bar(x + width/2, sorted_vals, width, label='Sorted')
|
||||||
|
ax.set_xticks(x)
|
||||||
|
ax.set_xticklabels(structures)
|
||||||
|
ax.set_ylabel('Time (seconds)')
|
||||||
|
ax.set_title(title)
|
||||||
|
ax.legend()
|
||||||
|
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig('performance_comparison.png', dpi=150)
|
||||||
|
plt.show()
|
||||||
16
BoriskovaDV/docs/data/2-nd-exercise/experiment_results.csv
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
maze,strategy,time_ms,visited_cells,path_length
|
||||||
|
Small 10x6,BFS,0.05722500009142095,25.0,16.0
|
||||||
|
Small 10x6,DFS,0.05680966667872175,24.0,16.0
|
||||||
|
Small 10x6,AStar,0.04801966664066034,23.0,16.0
|
||||||
|
Medium 10x10,BFS,0.04772166676048073,47.0,16.0
|
||||||
|
Medium 10x10,DFS,0.034641333362136116,44.0,30.0
|
||||||
|
Medium 10x10,AStar,0.0983669999641279,47.0,16.0
|
||||||
|
Large 20x20,BFS,0.09949400002066493,100.0,36.0
|
||||||
|
Large 20x20,DFS,0.07004933331700158,75.0,68.0
|
||||||
|
Large 20x20,AStar,0.16450733316257052,85.0,36.0
|
||||||
|
Empty 15x15,BFS,0.13264433331035738,133.0,17.0
|
||||||
|
Empty 15x15,DFS,0.11371733338213137,161.0,89.0
|
||||||
|
Empty 15x15,AStar,0.1543506666621397,65.0,17.0
|
||||||
|
No exit 10x10,BFS,0.04392100011803753,25.0,0.0
|
||||||
|
No exit 10x10,DFS,0.05871466661725814,25.0,0.0
|
||||||
|
No exit 10x10,AStar,0.046440666665148456,25.0,0.0
|
||||||
|
438
BoriskovaDV/docs/data/2-nd-exercise/main.py
Normal file
|
|
@ -0,0 +1,438 @@
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
from collections import deque
|
||||||
|
import heapq
|
||||||
|
import time
|
||||||
|
import csv
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
class GridPoint:
|
||||||
|
def __init__(self, x, y):
|
||||||
|
self.x = x
|
||||||
|
self.y = y
|
||||||
|
self.blocked = False
|
||||||
|
self.is_start = False
|
||||||
|
self.is_exit = False
|
||||||
|
|
||||||
|
def can_step(self):
|
||||||
|
return not self.blocked
|
||||||
|
|
||||||
|
class Labyrinth:
|
||||||
|
def __init__(self, w, h):
|
||||||
|
self.w = w
|
||||||
|
self.h = h
|
||||||
|
self.grid = [[GridPoint(x, y) for x in range(w)] for y in range(h)]
|
||||||
|
self.start_point = None
|
||||||
|
self.exit_point = None
|
||||||
|
|
||||||
|
def get_point(self, x, y):
|
||||||
|
if 0 <= x < self.w and 0 <= y < self.h:
|
||||||
|
return self.grid[y][x]
|
||||||
|
return None
|
||||||
|
|
||||||
|
def set_point(self, x, y, typ):
|
||||||
|
p = self.get_point(x, y)
|
||||||
|
if not p:
|
||||||
|
return
|
||||||
|
if typ == 'wall':
|
||||||
|
p.blocked = True
|
||||||
|
elif typ == 'start':
|
||||||
|
if self.start_point:
|
||||||
|
self.start_point.is_start = False
|
||||||
|
p.is_start = True
|
||||||
|
p.blocked = False
|
||||||
|
self.start_point = p
|
||||||
|
elif typ == 'exit':
|
||||||
|
if self.exit_point:
|
||||||
|
self.exit_point.is_exit = False
|
||||||
|
p.is_exit = True
|
||||||
|
p.blocked = False
|
||||||
|
self.exit_point = p
|
||||||
|
elif typ == 'path':
|
||||||
|
p.blocked = False
|
||||||
|
|
||||||
|
def neighbors(self, p):
|
||||||
|
dirs = [(0, -1), (0, 1), (-1, 0), (1, 0)]
|
||||||
|
res = []
|
||||||
|
for dx, dy in dirs:
|
||||||
|
nx, ny = p.x + dx, p.y + dy
|
||||||
|
nb = self.get_point(nx, ny)
|
||||||
|
if nb and nb.can_step():
|
||||||
|
res.append(nb)
|
||||||
|
return res
|
||||||
|
|
||||||
|
class MazeLoader:
|
||||||
|
def load(self, filename):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
class TextMazeLoader(MazeLoader):
|
||||||
|
def load(self, filename):
|
||||||
|
with open(filename, 'r') as f:
|
||||||
|
lines = [line.rstrip('\n') for line in f]
|
||||||
|
h = len(lines)
|
||||||
|
w = max(len(line) for line in lines) if h > 0 else 0
|
||||||
|
start_cnt = 0
|
||||||
|
exit_cnt = 0
|
||||||
|
lab = Labyrinth(w, h)
|
||||||
|
|
||||||
|
for y, line in enumerate(lines):
|
||||||
|
for x, ch in enumerate(line):
|
||||||
|
if ch == '#':
|
||||||
|
lab.set_point(x, y, 'wall')
|
||||||
|
elif ch == 'S':
|
||||||
|
lab.set_point(x, y, 'start')
|
||||||
|
start_cnt += 1
|
||||||
|
elif ch == 'E':
|
||||||
|
lab.set_point(x, y, 'exit')
|
||||||
|
exit_cnt += 1
|
||||||
|
else:
|
||||||
|
lab.set_point(x, y, 'path')
|
||||||
|
if start_cnt != 1 or exit_cnt != 1:
|
||||||
|
raise ValueError(f"Need exactly one S and one E. Found S={start_cnt}, E={exit_cnt}")
|
||||||
|
return lab
|
||||||
|
|
||||||
|
class SearchAlgorithm:
|
||||||
|
def find_way(self, lab, start, goal):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def _build_path(self, prev, start, goal):
|
||||||
|
path = []
|
||||||
|
cur = goal
|
||||||
|
while cur:
|
||||||
|
path.append(cur)
|
||||||
|
cur = prev.get(cur)
|
||||||
|
path.reverse()
|
||||||
|
return path
|
||||||
|
|
||||||
|
def get_visited(self):
|
||||||
|
return getattr(self, '_visited', 0)
|
||||||
|
|
||||||
|
class BreadthFirst(SearchAlgorithm):
|
||||||
|
def find_way(self, lab, start, goal):
|
||||||
|
q = deque([start])
|
||||||
|
prev = {start: None}
|
||||||
|
seen = {start}
|
||||||
|
while q:
|
||||||
|
cur = q.popleft()
|
||||||
|
if cur == goal:
|
||||||
|
self._visited = len(seen)
|
||||||
|
return self._build_path(prev, start, goal)
|
||||||
|
for nb in lab.neighbors(cur):
|
||||||
|
if nb not in seen:
|
||||||
|
seen.add(nb)
|
||||||
|
prev[nb] = cur
|
||||||
|
q.append(nb)
|
||||||
|
self._visited = len(seen)
|
||||||
|
return []
|
||||||
|
|
||||||
|
class DepthFirst(SearchAlgorithm):
|
||||||
|
def find_way(self, lab, start, goal):
|
||||||
|
stack = [start]
|
||||||
|
prev = {start: None}
|
||||||
|
seen = {start}
|
||||||
|
while stack:
|
||||||
|
cur = stack.pop()
|
||||||
|
if cur == goal:
|
||||||
|
self._visited = len(seen)
|
||||||
|
return self._build_path(prev, start, goal)
|
||||||
|
for nb in lab.neighbors(cur):
|
||||||
|
if nb not in seen:
|
||||||
|
seen.add(nb)
|
||||||
|
prev[nb] = cur
|
||||||
|
stack.append(nb)
|
||||||
|
self._visited = len(seen)
|
||||||
|
return []
|
||||||
|
|
||||||
|
class AStar(SearchAlgorithm):
|
||||||
|
def _dist(self, a, b):
|
||||||
|
return abs(a.x - b.x) + abs(a.y - b.y)
|
||||||
|
|
||||||
|
def find_way(self, lab, start, goal):
|
||||||
|
heap = []
|
||||||
|
cnt = 0
|
||||||
|
start_f = self._dist(start, goal)
|
||||||
|
heapq.heappush(heap, (start_f, cnt, start))
|
||||||
|
cnt += 1
|
||||||
|
prev = {}
|
||||||
|
g = {start: 0}
|
||||||
|
f = {start: start_f}
|
||||||
|
seen = set()
|
||||||
|
while heap:
|
||||||
|
cur_f, _, cur = heapq.heappop(heap)
|
||||||
|
seen.add(cur)
|
||||||
|
if cur == goal:
|
||||||
|
self._visited = len(seen)
|
||||||
|
return self._build_path(prev, start, goal)
|
||||||
|
if cur_f > f.get(cur, float('inf')):
|
||||||
|
continue
|
||||||
|
for nb in lab.neighbors(cur):
|
||||||
|
new_g = g[cur] + 1
|
||||||
|
if new_g < g.get(nb, float('inf')):
|
||||||
|
prev[nb] = cur
|
||||||
|
g[nb] = new_g
|
||||||
|
new_f = new_g + self._dist(nb, goal)
|
||||||
|
f[nb] = new_f
|
||||||
|
heapq.heappush(heap, (new_f, cnt, nb))
|
||||||
|
cnt += 1
|
||||||
|
self._visited = len(seen)
|
||||||
|
return []
|
||||||
|
|
||||||
|
class LabyrinthSolver:
|
||||||
|
def __init__(self, lab):
|
||||||
|
self.lab = lab
|
||||||
|
self.algorithm = None
|
||||||
|
|
||||||
|
def set_algorithm(self, algo):
|
||||||
|
self.algorithm = algo
|
||||||
|
|
||||||
|
def solve(self):
|
||||||
|
if not self.algorithm:
|
||||||
|
return None
|
||||||
|
t0 = time.perf_counter()
|
||||||
|
path = self.algorithm.find_way(self.lab, self.lab.start_point, self.lab.exit_point)
|
||||||
|
t1 = time.perf_counter()
|
||||||
|
ms = (t1 - t0) * 1000
|
||||||
|
return ms, self.algorithm.get_visited(), len(path)
|
||||||
|
|
||||||
|
class Player:
|
||||||
|
def __init__(self, start, lab):
|
||||||
|
self.current = start
|
||||||
|
self.last = None
|
||||||
|
self.lab = lab
|
||||||
|
|
||||||
|
def move(self, cell):
|
||||||
|
if cell and cell.can_step():
|
||||||
|
self.last = self.current
|
||||||
|
self.current = cell
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def undo(self):
|
||||||
|
if self.last:
|
||||||
|
self.current, self.last = self.last, None
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
class Command:
|
||||||
|
def do(self):
|
||||||
|
raise NotImplementedError
|
||||||
|
def revert(self):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
class MoveCommand(Command):
|
||||||
|
def __init__(self, player, dx, dy, lab):
|
||||||
|
self.player = player
|
||||||
|
self.dx = dx
|
||||||
|
self.dy = dy
|
||||||
|
self.lab = lab
|
||||||
|
self.done = False
|
||||||
|
|
||||||
|
def do(self):
|
||||||
|
nx = self.player.current.x + self.dx
|
||||||
|
ny = self.player.current.y + self.dy
|
||||||
|
target = self.lab.get_point(nx, ny)
|
||||||
|
if target and target.can_step():
|
||||||
|
self.player.move(target)
|
||||||
|
self.done = True
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def revert(self):
|
||||||
|
if self.done:
|
||||||
|
self.player.undo()
|
||||||
|
self.done = False
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
class InteractiveView:
|
||||||
|
def __init__(self, lab, player):
|
||||||
|
self.lab = lab
|
||||||
|
self.player = player
|
||||||
|
|
||||||
|
def render(self):
|
||||||
|
os.system('cls' if os.name == 'nt' else 'clear')
|
||||||
|
print("=" * (self.lab.w * 2 + 4))
|
||||||
|
print(" LABYRINTH (P = player)")
|
||||||
|
print("=" * (self.lab.w * 2 + 4))
|
||||||
|
for y in range(self.lab.h):
|
||||||
|
print(" ", end='')
|
||||||
|
for x in range(self.lab.w):
|
||||||
|
p = self.lab.get_point(x, y)
|
||||||
|
if self.player.current == p:
|
||||||
|
print('P', end=' ')
|
||||||
|
elif p == self.lab.start_point:
|
||||||
|
print('S', end=' ')
|
||||||
|
elif p == self.lab.exit_point:
|
||||||
|
print('E', end=' ')
|
||||||
|
elif p.blocked:
|
||||||
|
print('#', end=' ')
|
||||||
|
else:
|
||||||
|
print('.', end=' ')
|
||||||
|
print()
|
||||||
|
print("=" * (self.lab.w * 2 + 4))
|
||||||
|
print(f" Position: ({self.player.current.x},{self.player.current.y})")
|
||||||
|
print(" Controls: h(left) j(down) k(up) l(right) u=undo q=quit")
|
||||||
|
print(" Auto-search: b=BFS d=DFS a=A*")
|
||||||
|
|
||||||
|
def run_experiment(maze_file, algo, runs=5):
|
||||||
|
loader = TextMazeLoader()
|
||||||
|
lab = loader.load(maze_file)
|
||||||
|
total_ms = 0
|
||||||
|
total_visited = 0
|
||||||
|
total_len = 0
|
||||||
|
for _ in range(runs):
|
||||||
|
solver = LabyrinthSolver(lab)
|
||||||
|
solver.set_algorithm(algo)
|
||||||
|
stats = solver.solve()
|
||||||
|
if stats:
|
||||||
|
ms, vis, plen = stats
|
||||||
|
total_ms += ms
|
||||||
|
total_visited += vis
|
||||||
|
total_len += plen
|
||||||
|
return total_ms / runs, total_visited / runs, total_len / runs
|
||||||
|
|
||||||
|
def generate_plots(results):
|
||||||
|
mazes = list(set([r['maze'] for r in results]))
|
||||||
|
strategies = ['BFS', 'DFS', 'AStar']
|
||||||
|
|
||||||
|
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
|
||||||
|
x = np.arange(len(mazes))
|
||||||
|
width = 0.25
|
||||||
|
|
||||||
|
for i, strat in enumerate(strategies):
|
||||||
|
times = []
|
||||||
|
for maze in mazes:
|
||||||
|
val = next((r['time_ms'] for r in results if r['maze'] == maze and r['strategy'] == strat), 0)
|
||||||
|
times.append(val)
|
||||||
|
axes[0].bar(x + i*width, times, width, label=strat)
|
||||||
|
axes[0].set_xlabel('Maze')
|
||||||
|
axes[0].set_ylabel('Time (ms)')
|
||||||
|
axes[0].set_title('Execution Time')
|
||||||
|
axes[0].set_xticks(x + width)
|
||||||
|
axes[0].set_xticklabels(mazes, rotation=45, ha='right')
|
||||||
|
axes[0].legend()
|
||||||
|
axes[0].grid(True, alpha=0.3)
|
||||||
|
|
||||||
|
for i, strat in enumerate(strategies):
|
||||||
|
visited = []
|
||||||
|
for maze in mazes:
|
||||||
|
val = next((r['visited_cells'] for r in results if r['maze'] == maze and r['strategy'] == strat), 0)
|
||||||
|
visited.append(val)
|
||||||
|
axes[1].bar(x + i*width, visited, width, label=strat)
|
||||||
|
axes[1].set_xlabel('Maze')
|
||||||
|
axes[1].set_ylabel('Visited Cells')
|
||||||
|
axes[1].set_title('Visited Cells')
|
||||||
|
axes[1].set_xticks(x + width)
|
||||||
|
axes[1].set_xticklabels(mazes, rotation=45, ha='right')
|
||||||
|
axes[1].legend()
|
||||||
|
axes[1].grid(True, alpha=0.3)
|
||||||
|
|
||||||
|
for i, strat in enumerate(strategies):
|
||||||
|
lengths = []
|
||||||
|
for maze in mazes:
|
||||||
|
val = next((r['path_length'] for r in results if r['maze'] == maze and r['strategy'] == strat), 0)
|
||||||
|
lengths.append(val)
|
||||||
|
axes[2].bar(x + i*width, lengths, width, label=strat)
|
||||||
|
axes[2].set_xlabel('Maze')
|
||||||
|
axes[2].set_ylabel('Path Length')
|
||||||
|
axes[2].set_title('Path Length')
|
||||||
|
axes[2].set_xticks(x + width)
|
||||||
|
axes[2].set_xticklabels(mazes, rotation=45, ha='right')
|
||||||
|
axes[2].legend()
|
||||||
|
axes[2].grid(True, alpha=0.3)
|
||||||
|
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig('performance_comparison.png', dpi=150, bbox_inches='tight')
|
||||||
|
plt.show()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
if len(sys.argv) > 1 and sys.argv[1] == 'experiment':
|
||||||
|
print("Running experiments on all mazes...")
|
||||||
|
maze_files = [
|
||||||
|
("maze/maze1.txt", "Small 10x6"),
|
||||||
|
("maze/maze10x10.txt", "Medium 10x10"),
|
||||||
|
("maze/maze20x20.txt", "Large 20x20"),
|
||||||
|
("maze/maze_empty.txt", "Empty 15x15"),
|
||||||
|
("maze/maze_no_exit.txt", "No exit 10x10")
|
||||||
|
]
|
||||||
|
algorithms = [
|
||||||
|
("BFS", BreadthFirst()),
|
||||||
|
("DFS", DepthFirst()),
|
||||||
|
("AStar", AStar())
|
||||||
|
]
|
||||||
|
results = []
|
||||||
|
for fname, label in maze_files:
|
||||||
|
print(f"Testing {label}...")
|
||||||
|
for aname, algo in algorithms:
|
||||||
|
try:
|
||||||
|
avg_t, avg_v, avg_l = run_experiment(fname, algo, runs=3)
|
||||||
|
results.append({
|
||||||
|
'maze': label,
|
||||||
|
'strategy': aname,
|
||||||
|
'time_ms': avg_t,
|
||||||
|
'visited_cells': avg_v,
|
||||||
|
'path_length': avg_l
|
||||||
|
})
|
||||||
|
print(f" {aname}: time={avg_t:.3f}ms visited={avg_v:.0f} length={avg_l:.0f}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f" {aname}: ERROR {e}")
|
||||||
|
# save csv
|
||||||
|
with open('experiment_results.csv', 'w', newline='', encoding='utf-8') as f:
|
||||||
|
writer = csv.DictWriter(f, fieldnames=['maze', 'strategy', 'time_ms', 'visited_cells', 'path_length'])
|
||||||
|
writer.writeheader()
|
||||||
|
writer.writerows(results)
|
||||||
|
generate_plots(results)
|
||||||
|
print("Done. Results saved to experiment_results.csv and performance_comparison.png")
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
# else interactive mode
|
||||||
|
loader = TextMazeLoader()
|
||||||
|
lab = loader.load("maze/maze1.txt")
|
||||||
|
player = Player(lab.start_point, lab)
|
||||||
|
view = InteractiveView(lab, player)
|
||||||
|
view.render()
|
||||||
|
|
||||||
|
solver = LabyrinthSolver(lab)
|
||||||
|
history = []
|
||||||
|
|
||||||
|
while True:
|
||||||
|
key = input("\n > ").lower()
|
||||||
|
if key == 'q':
|
||||||
|
print("Goodbye!")
|
||||||
|
break
|
||||||
|
elif key == 'b':
|
||||||
|
solver.set_algorithm(BreadthFirst())
|
||||||
|
ms, vis, plen = solver.solve()
|
||||||
|
print(f"BFS: {ms:.3f}ms, visited={vis}, length={plen}")
|
||||||
|
elif key == 'd':
|
||||||
|
solver.set_algorithm(DepthFirst())
|
||||||
|
ms, vis, plen = solver.solve()
|
||||||
|
print(f"DFS: {ms:.3f}ms, visited={vis}, length={plen}")
|
||||||
|
elif key == 'a':
|
||||||
|
solver.set_algorithm(AStar())
|
||||||
|
ms, vis, plen = solver.solve()
|
||||||
|
print(f"A*: {ms:.3f}ms, visited={vis}, length={plen}")
|
||||||
|
elif key in ('h','j','k','l'):
|
||||||
|
moves = {'h': (-1,0), 'l': (1,0), 'k': (0,-1), 'j': (0,1)}
|
||||||
|
dx, dy = moves[key]
|
||||||
|
cmd = MoveCommand(player, dx, dy, lab)
|
||||||
|
if cmd.do():
|
||||||
|
history.append(cmd)
|
||||||
|
view.render()
|
||||||
|
if player.current == lab.exit_point:
|
||||||
|
print("\n*** YOU REACHED THE EXIT! ***")
|
||||||
|
print(f"Total moves: {len(history)}")
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
print("Can't go there - wall!")
|
||||||
|
elif key == 'u':
|
||||||
|
if history:
|
||||||
|
cmd = history.pop()
|
||||||
|
cmd.revert()
|
||||||
|
view.render()
|
||||||
|
print("Undo last move")
|
||||||
|
else:
|
||||||
|
print("Nothing to undo")
|
||||||
|
else:
|
||||||
|
print("Unknown command")
|
||||||
7
BoriskovaDV/docs/data/2-nd-exercise/maze/maze1.txt
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
##########
|
||||||
|
#S #
|
||||||
|
# ####### #
|
||||||
|
# # # #
|
||||||
|
# # ### # #
|
||||||
|
# # E #
|
||||||
|
##########
|
||||||
10
BoriskovaDV/docs/data/2-nd-exercise/maze/maze10x10.txt
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
##########
|
||||||
|
#S #
|
||||||
|
# # #### #
|
||||||
|
# # #
|
||||||
|
# #### # #
|
||||||
|
# # #
|
||||||
|
# #### # #
|
||||||
|
# # #
|
||||||
|
# #
|
||||||
|
########E#
|
||||||
21
BoriskovaDV/docs/data/2-nd-exercise/maze/maze20x20.txt
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
####################
|
||||||
|
#S #
|
||||||
|
# ############### #
|
||||||
|
# # # #
|
||||||
|
# # ######### # # #
|
||||||
|
# # # # # # #
|
||||||
|
# # # ##### # # # #
|
||||||
|
# # # # # # # # #
|
||||||
|
# # # # # # # # # #
|
||||||
|
# # # # # # # # #
|
||||||
|
# # # ##### # # # #
|
||||||
|
# # # # # # #
|
||||||
|
# # ######### # # #
|
||||||
|
# # # #
|
||||||
|
# ############### #
|
||||||
|
# #
|
||||||
|
# ############### #
|
||||||
|
# # # #
|
||||||
|
# # ########### # #
|
||||||
|
# E#
|
||||||
|
####################
|
||||||
15
BoriskovaDV/docs/data/2-nd-exercise/maze/maze_empty.txt
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
###############
|
||||||
|
#S #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# E #
|
||||||
|
###############
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
##########
|
||||||
|
#S #
|
||||||
|
# # #
|
||||||
|
# # #### #
|
||||||
|
# # #
|
||||||
|
##########
|
||||||
|
E#########
|
||||||
BIN
BoriskovaDV/docs/data/2-nd-exercise/performance_comparison.png
Normal file
|
After Width: | Height: | Size: 84 KiB |
BIN
BoriskovaDV/docs/performance_comparison-2-nd-exercise.png
Normal file
|
After Width: | Height: | Size: 84 KiB |
BIN
BoriskovaDV/docs/performance_comparison.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
94
BoriskovaDV/docs/report1.md
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
# Отчёт по лабораторной работе «Структуры данных для телефонного справочника»
|
||||||
|
|
||||||
|
## 1. Постановка задачи
|
||||||
|
|
||||||
|
В рамках работы требовалось реализовать три структуры данных «с нуля» (без использования встроенных коллекций, кроме базовых списков):
|
||||||
|
|
||||||
|
- связный список,
|
||||||
|
- хеш-таблицу с цепочками,
|
||||||
|
- двоичное дерево поиска (несбалансированное).
|
||||||
|
|
||||||
|
Для каждой структуры необходимо реализовать операции `insert`, `find`, `delete` и `list_all` (возврат всех записей, отсортированных по имени). Затем на наборе из 10 000 записей выполнить экспериментальное сравнение производительности в двух режимах: при случайном порядке вставки и при вставке записей, отсортированных по имени. Каждый эксперимент повторялся 5 раз.
|
||||||
|
|
||||||
|
## 2. Результаты измерений
|
||||||
|
|
||||||
|
Ниже приведены усреднённые по 5 повторам времена выполнения операций (в секундах). Исходные сырые данные сохранены в файле `experiment_results.csv`.
|
||||||
|
|
||||||
|
| Структура | Режим | Вставка (с) | Поиск 110 имён (с) | Удаление 50 записей (с) |
|
||||||
|
|-------------|-------------|-------------|--------------------|-------------------------|
|
||||||
|
| LinkedList | случайный | 4.7265 | 0.0345 | 0.0154 |
|
||||||
|
| LinkedList | сортир. | 4.4403 | 0.0275 | 0.0119 |
|
||||||
|
| HashTable | случайный | 0.6677 | 0.0051 | 0.0019 |
|
||||||
|
| HashTable | сортир. | 0.6460 | 0.0040 | 0.0021 |
|
||||||
|
| BST | случайный | 0.0277 | 0.00029 | 0.00016 |
|
||||||
|
| BST | сортир. | 10.1642 | 0.0886 | 0.0542 |
|
||||||
|
|
||||||
|
### Примечания к методике
|
||||||
|
|
||||||
|
- **Вставка** – добавление всех 10 000 записей в пустую структуру.
|
||||||
|
- **Поиск** – 100 заведомо существующих имён + 10 несуществующих (общее количество вызовов 110).
|
||||||
|
- **Удаление** – 50 случайных существующих записей.
|
||||||
|
- Все замеры выполнены с помощью `time.perf_counter()`.
|
||||||
|
- Для хеш-таблицы использовалось 10 корзин.
|
||||||
|
- Рекурсивная глубина BST увеличена до 20 000, чтобы избежать переполнения стека.
|
||||||
|
|
||||||
|
## 3. Анализ полученных данных
|
||||||
|
|
||||||
|
### 3.1. Поведение BST при разных порядках ввода
|
||||||
|
|
||||||
|
Двоичное дерево поиска сильно зависит от порядка поступления ключей. При случайном порядке средняя высота близка к логарифмической, что даёт отличную производительность:
|
||||||
|
|
||||||
|
- вставка – **0.0277 с**,
|
||||||
|
- поиск – **0.00029 с** (самый быстрый среди всех структур в этом режиме).
|
||||||
|
|
||||||
|
Однако при вставке отсортированных данных дерево вырождается в линейный список (каждый новый узел добавляется только в правое поддерево). Последствия:
|
||||||
|
|
||||||
|
- время вставки возрастает **в 367 раз** (с 0.0277 до 10.16 с),
|
||||||
|
- поиск замедляется **в 305 раз**,
|
||||||
|
- удаление – **в 339 раз**.
|
||||||
|
|
||||||
|
Вырожденное BST на отсортированных данных работает **медленнее даже связного списка** (вставка 10.16 с против 4.44 с, поиск 0.088 с против 0.027 с), что объясняется накладными расходами на рекурсивные вызовы и проверки.
|
||||||
|
|
||||||
|
### 3.2. Хеш-таблица – устойчивость к порядку
|
||||||
|
|
||||||
|
Хеш-таблица использует функцию `hash(name) % size`, которая равномерно рассеивает имена независимо от их лексикографического порядка. Поэтому результаты в двух режимах практически идентичны:
|
||||||
|
|
||||||
|
- вставка: 0.668 с (случайный) против 0.646 с (отсортированный) – разница менее 4%,
|
||||||
|
- поиск: 0.0051 с против 0.0040 с,
|
||||||
|
- удаление: 0.0019 с против 0.0021 с.
|
||||||
|
|
||||||
|
Небольшие расхождения находятся в пределах случайной вариации (зависит от коллизий, которые немного различаются при разном порядке вставки). Средняя сложность операций остаётся **O(1)**.
|
||||||
|
|
||||||
|
### 3.3. Связный список – ожидаемо медленный
|
||||||
|
|
||||||
|
Линейный список не обеспечивает прямого доступа, поэтому все операции (кроме удаления после нахождения) требуют обхода в среднем половины списка. Даже при сравнительно небольшом объёме данных (10 000 записей) времена велики:
|
||||||
|
|
||||||
|
- вставка ≈ **4.6 с** (на два порядка хуже, чем у хеш-таблицы и BST на случайных данных),
|
||||||
|
- поиск ≈ **0.03 с** (в 6–10 раз медленнее, чем у других структур).
|
||||||
|
|
||||||
|
Интересно, что на отсортированных данных список показывает немного лучшее время, чем на случайных. Причина: при вставке в конец отсортированного списка (имена идут в алфавитном порядке) новые узлы добавляются без поиска дубликатов? Но алгоритм `ll_insert` сначала проверяет наличие имени, проходя весь список. Поскольку все имена уникальны и не обновляются, каждый проход идёт до конца. Однако в отсортированном режиме имена добавляются в порядке возрастания, и при проверке дубликата мы проходим по уже существующим элементам, которые все меньше нового? Да, в отсортированном режиме каждое новое имя больше всех предыдущих, поэтому при поиске дубликата мы обходим весь существующий список. В случайном режиме новые имена могут встречаться раньше, и поиск останавливается раньше? Но в любом случае разница небольшая (около 6%), и в целом список остаётся медленным.
|
||||||
|
|
||||||
|
### 3.4. Сравнение удаления
|
||||||
|
|
||||||
|
Удаление в списке требует сначала найти элемент (O(n)), затем перелинковку. В хеш-таблице удаление сводится к удалению в коротком списке корзины (почти O(1)). В BST на случайных данных удаление очень быстрое (0.00016 с), на отсортированных – катастрофически замедляется (0.054 с). Для хеш-таблицы удаление немного быстрее, чем вставка, что естественно: при удалении не нужно создавать новый узел.
|
||||||
|
|
||||||
|
## 4. Выводы и практические рекомендации
|
||||||
|
|
||||||
|
Проведённое исследование наглядно демонстрирует сильные и слабые стороны каждой структуры.
|
||||||
|
|
||||||
|
1. **Хеш-таблица** – лучший выбор для задач, где приоритетом является скорость всех операций (вставка, поиск, удаление), а порядок вывода данных не важен или может быть получен отдельной сортировкой. Стабильно высокая производительность вне зависимости от характера входных данных. В реальных проектах именно хеш-таблицы лежат в основе словарей (Python `dict`, Java `HashMap`).
|
||||||
|
|
||||||
|
2. **Двоичное дерево поиска** – эффективно только при случайном или близком к случайному порядке поступления ключей. Даёт логарифмическую сложность и при этом позволяет получать данные в отсортированном виде за O(n) без дополнительной сортировки. Однако на реальных данных (например, заведомо отсортированных) производительность падает до O(n), что делает его непригодным без механизмов балансировки. На практике применяются сбалансированные варианты (AVL, красно-чёрные деревья).
|
||||||
|
|
||||||
|
3. **Связный список** – не подходит для коллекций объёмом более нескольких сотен элементов из-за линейной сложности основных операций. Может использоваться только в очень специфических сценариях: очень редкий поиск, постоянные вставки/удаления в начало (но не в конец), или как строительный блок для других структур (например, для цепочек в хеш-таблице, что и было сделано в данной работе).
|
||||||
|
|
||||||
|
### Итоговая таблица применимости
|
||||||
|
|
||||||
|
| Критерий | Рекомендуемая структура |
|
||||||
|
|---------------------------------|---------------------------------------|
|
||||||
|
| Максимальная скорость всех операций | Хеш-таблица |
|
||||||
|
| Нужны данные в отсортированном порядке + данные поступают случайно | BST (но лучше сбалансированное) |
|
||||||
|
| Данные поступают уже отсортированными | Хеш-таблица (или балансируемое дерево) |
|
||||||
|
| Очень маленький объём (< 100 записей) | Любая, но проще список |
|
||||||
|
|
||||||
|
В реальной разработке для телефонного справочника с большим числом записей и частыми запросами поиска оптимальным решением будет **хеш-таблица**. Если же дополнительно требуется частый вывод всего справочника по алфавиту, стоит рассмотреть сбалансированное дерево (например, встроенный в Python модуль `bisect` не даёт структуры данных, а `sortedcontainers` – сторонний).
|
||||||
92
BoriskovaDV/docs/report2.md
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
# Отчёт по лабораторной работе: Алгоритмы поиска пути в лабиринте
|
||||||
|
|
||||||
|
## 1. Цель работы
|
||||||
|
|
||||||
|
Разработка программы для загрузки лабиринта из текстового файла, реализации трёх алгоритмов поиска пути (BFS, DFS, A\*) и проведения экспериментального сравнения их эффективности на лабиринтах различной сложности.
|
||||||
|
|
||||||
|
## 2. Структура программы
|
||||||
|
|
||||||
|
Программа написана на Python 3 и состоит из следующих основных классов:
|
||||||
|
|
||||||
|
- `GridPoint` – представление клетки лабиринта (координаты, проходимость, флаги старта/выхода);
|
||||||
|
- `Labyrinth` – модель лабиринта (сетка клеток, методы получения соседей);
|
||||||
|
- `TextMazeLoader` – загрузка лабиринта из файла с символами `#` (стена), `S` (старт), `E` (выход);
|
||||||
|
- `SearchAlgorithm` (и наследники `BreadthFirst`, `DepthFirst`, `AStar`) – реализация алгоритмов поиска;
|
||||||
|
- `LabyrinthSolver` – класс-оркестратор, позволяющий сменить стратегию и измеряющий время выполнения;
|
||||||
|
- `Player`, `Command`, `MoveCommand`, `InteractiveView` – для интерактивного режима с отменой ходов;
|
||||||
|
- функции `run_experiment` и `generate_plots` – для многократных запусков и построения графиков.
|
||||||
|
|
||||||
|
## 3. Описание алгоритмов
|
||||||
|
|
||||||
|
### 3.1 BFS (поиск в ширину)
|
||||||
|
Использует очередь. Гарантирует нахождение кратчайшего пути (по числу шагов). Обходит клетки в порядке увеличения расстояния от старта.
|
||||||
|
|
||||||
|
### 3.2 DFS (поиск в глубину)
|
||||||
|
Использует стек. Идёт «вглубь» по одному пути, не гарантирует кратчайший путь. Обычно быстрее по времени и памяти на больших лабиринтах.
|
||||||
|
|
||||||
|
### 3.3 A* (звездочка)
|
||||||
|
Использует приоритетную очередь и эвристику (манхэттенское расстояние). Оценивает клетку по формуле `f = g + h`, где `g` – пройденное расстояние, `h` – эвристика. Находит оптимальный путь, если эвристика допустима.
|
||||||
|
|
||||||
|
## 4. Методика эксперимента
|
||||||
|
|
||||||
|
Для каждого лабиринта каждый алгоритм запускался 3 раза, результаты усреднялись. Измерялись:
|
||||||
|
- время выполнения (в миллисекундах);
|
||||||
|
- количество посещённых клеток;
|
||||||
|
- длина найденного пути.
|
||||||
|
|
||||||
|
Тестовые лабиринты:
|
||||||
|
|
||||||
|
| Название | Размер | Описание |
|
||||||
|
|----------|--------|-----------|
|
||||||
|
| Small 10x6 | 10×6 | Простой лабиринт с извилистым коридором |
|
||||||
|
| Medium 10x10 | 10×10 | Лабиринт среднего размера с несколькими тупиками |
|
||||||
|
| Large 20x20 | 20×20 | Большой запутанный лабиринт |
|
||||||
|
| Empty 15x15 | 15×15 | Пустой лабиринт без стен (прямая линия от S до E) |
|
||||||
|
| No exit 10x10 | 10×10 | Лабиринт без буквы E (путь отсутствует) |
|
||||||
|
|
||||||
|
## 5. Результаты экспериментов
|
||||||
|
|
||||||
|
| Лабиринт | Алгоритм | Время, мс | Посещено клеток | Длина пути |
|
||||||
|
|----------------|----------|-----------|-----------------|------------|
|
||||||
|
| Small 10x6 | BFS | 0.057 | 25 | 16 |
|
||||||
|
| Small 10x6 | DFS | 0.057 | 24 | 16 |
|
||||||
|
| Small 10x6 | A* | 0.048 | 23 | 16 |
|
||||||
|
| Medium 10x10 | BFS | 0.048 | 47 | 16 |
|
||||||
|
| Medium 10x10 | DFS | 0.035 | 44 | 30 |
|
||||||
|
| Medium 10x10 | A* | 0.098 | 47 | 16 |
|
||||||
|
| Large 20x20 | BFS | 0.099 | 100 | 36 |
|
||||||
|
| Large 20x20 | DFS | 0.070 | 75 | 68 |
|
||||||
|
| Large 20x20 | A* | 0.165 | 85 | 36 |
|
||||||
|
| Empty 15x15 | BFS | 0.133 | 133 | 17 |
|
||||||
|
| Empty 15x15 | DFS | 0.114 | 161 | 89 |
|
||||||
|
| Empty 15x15 | A* | 0.154 | 65 | 17 |
|
||||||
|
| No exit 10x10 | BFS | 0.044 | 25 | 0 |
|
||||||
|
| No exit 10x10 | DFS | 0.059 | 25 | 0 |
|
||||||
|
| No exit 10x10 | A* | 0.046 | 25 | 0 |
|
||||||
|
|
||||||
|
## 6. Анализ результатов
|
||||||
|
|
||||||
|
### 6.1. Нахождение кратчайшего пути
|
||||||
|
- **BFS** и **A*** нашли оптимальные пути во всех лабиринтах, где выход существовал (длина пути совпадает для них в каждом случае).
|
||||||
|
- **DFS** в лабиринтах Medium, Large и Empty дал существенно более длинные пути (30 против 16, 68 против 36, 89 против 17), что характерно для глубинного обхода без эвристики.
|
||||||
|
|
||||||
|
### 6.2. Время выполнения
|
||||||
|
- На малых лабиринтах все алгоритмы работают сопоставимо (0.035–0.099 мс).
|
||||||
|
- На лабиринте Large 20×20 BFS выполнился за 0.099 мс, A* – 0.165 мс (медленнее из-за сложности поддержки очереди с приоритетом), DFS – быстрее всех (0.070 мс).
|
||||||
|
- В пустом лабиринте BFS и A* обошли почти все клетки (133 и 65 посещённых соответственно), но A* за счёт эвристики посетил вдвое меньше клеток, хотя время оказалось чуть выше, чем у BFS (0.154 против 0.133 мс). Это объясняется накладными расходами на вычисление эвристики и управление кучей.
|
||||||
|
|
||||||
|
### 6.3. Количество посещённых клеток
|
||||||
|
- **A*** показал лучшую эффективность в пустом лабиринте (65 посещённых против 133 у BFS и 161 у DFS). В лабиринтах со стенами разница не столь заметна, но A* почти всегда посещал меньше клеток, чем BFS.
|
||||||
|
- **DFS** в среднем посещает меньше клеток, чем BFS, но при этом путь часто неоптимален.
|
||||||
|
- **BFS** вынужден обходить всю область равных расстояний, поэтому посещённых клеток обычно больше.
|
||||||
|
|
||||||
|
### 6.4. Поведение при отсутствии выхода
|
||||||
|
Все алгоритмы корректно завершились, вернув пустой путь (длина 0). В лабиринте без выхода BFS, DFS и A* посетили 25 клеток – это все доступные клетки.
|
||||||
|
|
||||||
|
## 7. Выводы
|
||||||
|
|
||||||
|
1. **BFS** надёжен для поиска кратчайшего пути, но может быть медленнее на больших открытых пространствах из-за широкого обхода.
|
||||||
|
2. **DFS** – самый быстрый по времени и экономный по памяти, но не гарантирует оптимальность пути. Его применение оправдано, когда любой путь подходит.
|
||||||
|
3. **A*** демонстрирует лучший баланс: находит кратчайший путь и при этом посещает меньше клеток, чем BFS. Небольшое замедление на сложных лабиринтах компенсируется меньшим числом обработанных клеток.
|
||||||
|
4. Программа успешно справляется с лабиринтами разного размера и конфигурации, включая отсутствие выхода.
|
||||||
|
5. Интерактивный режим с отменой ходов (паттерн Command) и выбором алгоритма (паттерн Strategy) реализован и работает корректно.
|
||||||
0
BorisovMI/429.md
Normal file
725
BorisovMI/lab_2/docs/data/maze.py
Normal file
|
|
@ -0,0 +1,725 @@
|
||||||
|
from abc import ABC, abstractclassmethod
|
||||||
|
from collections import deque
|
||||||
|
import heapq
|
||||||
|
import time
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
import csv
|
||||||
|
import random
|
||||||
|
class Cell:
|
||||||
|
def __init__(self, x, y):
|
||||||
|
self.x = x
|
||||||
|
self.y = y
|
||||||
|
self.isWall = False
|
||||||
|
self.isStart = False
|
||||||
|
self.isExit = False
|
||||||
|
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if other is None:
|
||||||
|
return False
|
||||||
|
return self.x == other.x and self.y == other.y
|
||||||
|
def __lt__(self, other):
|
||||||
|
|
||||||
|
if other is None:
|
||||||
|
return False
|
||||||
|
return (self.x, self.y) < (other.x, other.y)
|
||||||
|
def __hash__(self):
|
||||||
|
|
||||||
|
return hash((self.x, self.y))
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"Cell({self.x}, {self.y})"
|
||||||
|
def isPassable(self):
|
||||||
|
return not self.isWall
|
||||||
|
|
||||||
|
class Maze:
|
||||||
|
def __init__(self, width, height):
|
||||||
|
self.width = width
|
||||||
|
self.height = height
|
||||||
|
self.grid = [[Cell(x, y) for y in range(height)] for x in range(width)]
|
||||||
|
self.start = None
|
||||||
|
self.exit = None
|
||||||
|
|
||||||
|
def getCell(self, x, y):
|
||||||
|
if 0 <= x < self.width and 0 <= y < self.height:
|
||||||
|
return self.grid[x][y]
|
||||||
|
return None
|
||||||
|
|
||||||
|
def getNeighbors(self, cell):
|
||||||
|
neighbors = []
|
||||||
|
directions = [(0, 1), (0, -1), (1, 0), (-1, 0)]
|
||||||
|
for dx, dy in directions:
|
||||||
|
neighbor = self.getCell(cell.x + dx, cell.y + dy)
|
||||||
|
if neighbor and neighbor.isPassable():
|
||||||
|
neighbors.append(neighbor)
|
||||||
|
return neighbors
|
||||||
|
|
||||||
|
def setStart(self, x, y):
|
||||||
|
cell = self.getCell(x, y)
|
||||||
|
if cell:
|
||||||
|
cell.isStart = True
|
||||||
|
self.start = cell
|
||||||
|
|
||||||
|
def setExit(self, x, y):
|
||||||
|
cell = self.getCell(x, y)
|
||||||
|
if cell:
|
||||||
|
cell.isExit = True
|
||||||
|
self.exit = cell
|
||||||
|
|
||||||
|
class MazeBuilder(ABC):
|
||||||
|
|
||||||
|
def buildFromFile(self, filename):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class TextileMazeBuilder(MazeBuilder):
|
||||||
|
def buildFromFile(self, filename):
|
||||||
|
with open(filename, 'r', encoding='utf-8') as f:
|
||||||
|
lines = f.readlines()
|
||||||
|
|
||||||
|
|
||||||
|
lines = [line.rstrip('\n\r') for line in lines]
|
||||||
|
|
||||||
|
height = len(lines)
|
||||||
|
width = len(lines[0]) if height > 0 else 0
|
||||||
|
|
||||||
|
|
||||||
|
for line in lines:
|
||||||
|
if len(line) != width:
|
||||||
|
raise ValueError("все строки одинаковой длины")
|
||||||
|
|
||||||
|
|
||||||
|
maze = Maze(width, height)
|
||||||
|
|
||||||
|
|
||||||
|
for y in range(height):
|
||||||
|
for x in range(width):
|
||||||
|
char = lines[y][x]
|
||||||
|
cell = maze.getCell(x, y)
|
||||||
|
|
||||||
|
if char == '#':
|
||||||
|
cell.isWall = True
|
||||||
|
elif char == ' ':
|
||||||
|
cell.isWall = False
|
||||||
|
elif char == 's':
|
||||||
|
cell.isWall = False
|
||||||
|
cell.isStart = True
|
||||||
|
maze.start = cell
|
||||||
|
elif char == 'e':
|
||||||
|
cell.isWall = False
|
||||||
|
cell.isExit = True
|
||||||
|
maze.exit = cell
|
||||||
|
else:
|
||||||
|
raise ValueError(f"неизв сим")
|
||||||
|
|
||||||
|
|
||||||
|
if maze.start is None:
|
||||||
|
raise ValueError("в лабиринте не найден старт")
|
||||||
|
if maze.exit is None:
|
||||||
|
raise ValueError("в лабиринте не найден выход")
|
||||||
|
|
||||||
|
return maze
|
||||||
|
|
||||||
|
class PathFindingStrategy:
|
||||||
|
def findPath(self, maze, start, exit):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class BFSStrategy(PathFindingStrategy):
|
||||||
|
def findPath(self, maze, start, exit):
|
||||||
|
if exit is None:
|
||||||
|
return []
|
||||||
|
queue = deque([start])
|
||||||
|
visited = {start}
|
||||||
|
parent = {start: None}
|
||||||
|
|
||||||
|
while queue:
|
||||||
|
current = queue.popleft()
|
||||||
|
|
||||||
|
if current == exit:
|
||||||
|
return self._reconstruct_path(parent, start, exit)
|
||||||
|
|
||||||
|
for neighbor in maze.getNeighbors(current):
|
||||||
|
if neighbor not in visited:
|
||||||
|
visited.add(neighbor)
|
||||||
|
parent[neighbor] = current
|
||||||
|
queue.append(neighbor)
|
||||||
|
|
||||||
|
return []
|
||||||
|
|
||||||
|
def _reconstruct_path(self, parent, start, exit):
|
||||||
|
path = []
|
||||||
|
current = exit
|
||||||
|
while current is not None:
|
||||||
|
path.append(current)
|
||||||
|
current = parent[current]
|
||||||
|
path.reverse()
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
class DFSStrategy(PathFindingStrategy):
|
||||||
|
def findPath(self, maze, start, exit):
|
||||||
|
if exit is None:
|
||||||
|
return []
|
||||||
|
stack = [start]
|
||||||
|
visited = {start}
|
||||||
|
parent = {start: None}
|
||||||
|
|
||||||
|
while stack:
|
||||||
|
current = stack.pop()
|
||||||
|
|
||||||
|
if current == exit:
|
||||||
|
return self._reconstruct_path(parent, start, exit)
|
||||||
|
|
||||||
|
for neighbor in maze.getNeighbors(current):
|
||||||
|
if neighbor not in visited:
|
||||||
|
visited.add(neighbor)
|
||||||
|
parent[neighbor] = current
|
||||||
|
stack.append(neighbor)
|
||||||
|
|
||||||
|
return []
|
||||||
|
|
||||||
|
def _reconstruct_path(self, parent, start, exit):
|
||||||
|
path = []
|
||||||
|
current = exit
|
||||||
|
while current is not None:
|
||||||
|
path.append(current)
|
||||||
|
current = parent[current]
|
||||||
|
path.reverse()
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
class AStrategy(PathFindingStrategy):
|
||||||
|
def _heuristic(self, cell, exit):
|
||||||
|
if exit is None:
|
||||||
|
return 0
|
||||||
|
return abs(cell.x - exit.x) + abs(cell.y - exit.y)
|
||||||
|
|
||||||
|
def findPath(self, maze, start, exit):
|
||||||
|
if exit is None:
|
||||||
|
return []
|
||||||
|
open_set = []
|
||||||
|
heapq.heappush(open_set, (0, start))
|
||||||
|
|
||||||
|
came_from = {start: None}
|
||||||
|
g_score = {start: 0}
|
||||||
|
|
||||||
|
while open_set:
|
||||||
|
current = heapq.heappop(open_set)[1]
|
||||||
|
|
||||||
|
if current == exit:
|
||||||
|
return self._reconstruct_path(came_from, start, exit)
|
||||||
|
|
||||||
|
for neighbor in maze.getNeighbors(current):
|
||||||
|
tentative_g = g_score[current] + 1
|
||||||
|
|
||||||
|
if neighbor not in g_score or tentative_g < g_score[neighbor]:
|
||||||
|
came_from[neighbor] = current
|
||||||
|
g_score[neighbor] = tentative_g
|
||||||
|
f_score = tentative_g + self._heuristic(neighbor, exit)
|
||||||
|
heapq.heappush(open_set, (f_score, neighbor))
|
||||||
|
|
||||||
|
return []
|
||||||
|
|
||||||
|
def _reconstruct_path(self, came_from, start, exit):
|
||||||
|
path = []
|
||||||
|
current = exit
|
||||||
|
while current is not None:
|
||||||
|
path.append(current)
|
||||||
|
current = came_from[current]
|
||||||
|
path.reverse()
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
class SearchStats:
|
||||||
|
def __init__(self, time_ms=0, visited_cells=0, path_length=0):
|
||||||
|
self.time_ms = time_ms
|
||||||
|
self.visited_cells = visited_cells
|
||||||
|
self.path_length = path_length
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"Время: {self.time_ms:.3f} мс | Посещено: {self.visited_cells} | Длина пути: {self.path_length}"
|
||||||
|
|
||||||
|
|
||||||
|
class MazeSolver:
|
||||||
|
def __init__(self, maze):
|
||||||
|
self.maze = maze
|
||||||
|
self.strategy = None
|
||||||
|
|
||||||
|
def setStrategy(self, strategy):
|
||||||
|
self.strategy = strategy
|
||||||
|
|
||||||
|
def solve(self):
|
||||||
|
if self.strategy is None:
|
||||||
|
raise ValueError("Стратегия не установлена")
|
||||||
|
|
||||||
|
|
||||||
|
start_time = time.perf_counter()
|
||||||
|
path = self.strategy.findPath(self.maze, self.maze.start, self.maze.exit)
|
||||||
|
|
||||||
|
end_time = time.perf_counter()
|
||||||
|
elapsed_ms = (end_time - start_time) * 1000
|
||||||
|
|
||||||
|
|
||||||
|
stats = SearchStats(
|
||||||
|
time_ms=elapsed_ms,
|
||||||
|
visited_cells=len(path),
|
||||||
|
path_length=len(path)
|
||||||
|
)
|
||||||
|
|
||||||
|
return path, stats
|
||||||
|
|
||||||
|
class Observer:
|
||||||
|
def update(self, event):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class ConsoleView(Observer):
|
||||||
|
def render(self, maze, player_position=None, path=None):
|
||||||
|
"""отрисовка"""
|
||||||
|
os.system('cls' if os.name == 'nt' else 'clear')
|
||||||
|
|
||||||
|
path_set = set(path) if path else set()
|
||||||
|
|
||||||
|
for y in range(maze.height):
|
||||||
|
for x in range(maze.width):
|
||||||
|
cell = maze.getCell(x, y)
|
||||||
|
|
||||||
|
if player_position and cell == player_position:
|
||||||
|
print('P', end='')
|
||||||
|
elif cell == maze.start:
|
||||||
|
print('S', end='')
|
||||||
|
elif cell == maze.exit:
|
||||||
|
print('E', end='')
|
||||||
|
elif cell in path_set:
|
||||||
|
print('.', end='')
|
||||||
|
elif cell.isWall:
|
||||||
|
print('#', end='')
|
||||||
|
else:
|
||||||
|
print(' ', end='')
|
||||||
|
print()
|
||||||
|
|
||||||
|
def update(self, event):
|
||||||
|
if event['type'] == 'path_found':
|
||||||
|
print(f"длина пути {len(event['path'])}")
|
||||||
|
self.render(event['maze'], path=event['path'])
|
||||||
|
elif event['type'] == 'move':
|
||||||
|
print(f"шаг {event['step']}")
|
||||||
|
self.render(event['maze'], event['player'], event['path'])
|
||||||
|
elif event['type'] == 'maze_loaded':
|
||||||
|
print("перегрузка")
|
||||||
|
self.render(event['maze'])
|
||||||
|
|
||||||
|
|
||||||
|
class ObservableMazeSolver:
|
||||||
|
def __init__(self, maze):
|
||||||
|
self.maze = maze
|
||||||
|
self.strategy = None
|
||||||
|
self.observers = []
|
||||||
|
|
||||||
|
def attach(self, observer):
|
||||||
|
self.observers.append(observer)
|
||||||
|
|
||||||
|
def notify(self, event):
|
||||||
|
for observer in self.observers:
|
||||||
|
observer.update(event)
|
||||||
|
|
||||||
|
def setStrategy(self, strategy):
|
||||||
|
self.strategy = strategy
|
||||||
|
|
||||||
|
def solve(self):
|
||||||
|
if self.strategy is None:
|
||||||
|
raise ValueError("")
|
||||||
|
|
||||||
|
|
||||||
|
path = self.strategy.findPath(self.maze, self.maze.start, self.maze.exit)
|
||||||
|
|
||||||
|
self.notify({
|
||||||
|
'type': 'path_found',
|
||||||
|
'maze': self.maze,
|
||||||
|
'path': path
|
||||||
|
})
|
||||||
|
|
||||||
|
return path
|
||||||
|
|
||||||
|
class Player:
|
||||||
|
def __init__(self, start_cell):
|
||||||
|
self.currentCell = start_cell
|
||||||
|
self.previousCell = None
|
||||||
|
|
||||||
|
def moveTo(self, cell):
|
||||||
|
self.previousCell = self.currentCell
|
||||||
|
self.currentCell = cell
|
||||||
|
|
||||||
|
def undoMove(self):
|
||||||
|
if self.previousCell:
|
||||||
|
self.currentCell, self.previousCell = self.previousCell, None
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class Command:
|
||||||
|
def execute(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def undo(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class MoveCommand(Command):
|
||||||
|
def __init__(self, player, direction, maze):
|
||||||
|
self.player = player
|
||||||
|
self.dx, self.dy = direction
|
||||||
|
self.maze = maze
|
||||||
|
self.executed = False
|
||||||
|
|
||||||
|
def execute(self):
|
||||||
|
new_x = self.player.currentCell.x + self.dx
|
||||||
|
new_y = self.player.currentCell.y + self.dy
|
||||||
|
new_cell = self.maze.getCell(new_x, new_y)
|
||||||
|
|
||||||
|
if new_cell and new_cell.isPassable():
|
||||||
|
self.player.moveTo(new_cell)
|
||||||
|
self.executed = True
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def undo(self):
|
||||||
|
if self.executed:
|
||||||
|
self.player.undoMove()
|
||||||
|
self.executed = False
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def clear_console():
|
||||||
|
os.system('cls' if os.name == 'nt' else 'clear')
|
||||||
|
|
||||||
|
def render_maze_with_player(maze, player, path=None):
|
||||||
|
path_set = set(path) if path else set()
|
||||||
|
|
||||||
|
for y in range(maze.height):
|
||||||
|
for x in range(maze.width):
|
||||||
|
cell = maze.getCell(x, y)
|
||||||
|
|
||||||
|
if cell == player.currentCell:
|
||||||
|
print('P', end='')
|
||||||
|
elif cell == maze.start:
|
||||||
|
print('S', end='')
|
||||||
|
elif cell == maze.exit:
|
||||||
|
print('E', end='')
|
||||||
|
elif cell in path_set:
|
||||||
|
print('.', end='')
|
||||||
|
elif cell.isWall:
|
||||||
|
print('#', end='')
|
||||||
|
else:
|
||||||
|
print(' ', end='')
|
||||||
|
print()
|
||||||
|
|
||||||
|
|
||||||
|
def run_game(maze, path=None):
|
||||||
|
player = Player(maze.start)
|
||||||
|
history = []
|
||||||
|
|
||||||
|
directions = {
|
||||||
|
'w': (0, -1),
|
||||||
|
's': (0, 1),
|
||||||
|
'a': (-1, 0),
|
||||||
|
'd': (1, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
print(" W/A/S/D - движение, U - отмена, Q - выход")
|
||||||
|
if path:
|
||||||
|
print(f"мин путь {len(path)} шагов")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
print()
|
||||||
|
render_maze_with_player(maze, player, path)
|
||||||
|
|
||||||
|
if player.currentCell == maze.exit:
|
||||||
|
print("\n*** выход ***")
|
||||||
|
break
|
||||||
|
|
||||||
|
key = input("\n> ").lower()
|
||||||
|
|
||||||
|
if key == 'q':
|
||||||
|
print("выход из игры")
|
||||||
|
break
|
||||||
|
elif key == 'u':
|
||||||
|
if history:
|
||||||
|
cmd = history.pop()
|
||||||
|
cmd.undo()
|
||||||
|
print("отмена хода")
|
||||||
|
else:
|
||||||
|
print("нет ходов")
|
||||||
|
elif key in directions:
|
||||||
|
cmd = MoveCommand(player, directions[key], maze)
|
||||||
|
if cmd.execute():
|
||||||
|
history.append(cmd)
|
||||||
|
else:
|
||||||
|
print("стена")
|
||||||
|
else:
|
||||||
|
print("неизвестно")
|
||||||
|
|
||||||
|
def generate_empty_maze(width, height):
|
||||||
|
|
||||||
|
maze = Maze(width, height)
|
||||||
|
for x in range(width):
|
||||||
|
for y in range(height):
|
||||||
|
maze.getCell(x, y).isWall = False
|
||||||
|
maze.setStart(0, 0)
|
||||||
|
maze.setExit(width-1, height-1)
|
||||||
|
return maze
|
||||||
|
|
||||||
|
def generate_maze_with_walls(width, height, wall_probability=0.3):
|
||||||
|
|
||||||
|
maze = Maze(width, height)
|
||||||
|
for x in range(width):
|
||||||
|
for y in range(height):
|
||||||
|
if random.random() < wall_probability:
|
||||||
|
maze.getCell(x, y).isWall = True
|
||||||
|
else:
|
||||||
|
maze.getCell(x, y).isWall = False
|
||||||
|
|
||||||
|
|
||||||
|
maze.getCell(0, 0).isWall = False
|
||||||
|
maze.getCell(width-1, height-1).isWall = False
|
||||||
|
|
||||||
|
maze.setStart(0, 0)
|
||||||
|
maze.setExit(width-1, height-1)
|
||||||
|
return maze
|
||||||
|
|
||||||
|
def generate_maze_no_exit(width, height):
|
||||||
|
|
||||||
|
maze = generate_maze_with_walls(width, height, 0.3)
|
||||||
|
|
||||||
|
exit_cell = maze.getCell(width-1, height-1)
|
||||||
|
exit_cell.isWall = True
|
||||||
|
maze.exit = None
|
||||||
|
return maze
|
||||||
|
|
||||||
|
def save_maze_to_file(maze, filename):
|
||||||
|
|
||||||
|
with open(filename, 'w') as f:
|
||||||
|
for y in range(maze.height):
|
||||||
|
for x in range(maze.width):
|
||||||
|
cell = maze.getCell(x, y)
|
||||||
|
if cell == maze.start:
|
||||||
|
f.write('s')
|
||||||
|
elif cell == maze.exit:
|
||||||
|
f.write('e')
|
||||||
|
elif cell.isWall:
|
||||||
|
f.write('#')
|
||||||
|
else:
|
||||||
|
f.write(' ')
|
||||||
|
f.write('\n')
|
||||||
|
|
||||||
|
def create_test_mazes():
|
||||||
|
|
||||||
|
mazes = []
|
||||||
|
|
||||||
|
|
||||||
|
small = generate_maze_with_walls(10, 10, 0.2)
|
||||||
|
save_maze_to_file(small, "maze_small.txt")
|
||||||
|
mazes.append(('маленький (10x10)', small))
|
||||||
|
|
||||||
|
|
||||||
|
medium = generate_maze_with_walls(50, 50, 0.3)
|
||||||
|
save_maze_to_file(medium, "maze_medium.txt")
|
||||||
|
mazes.append(('средний (50x50)', medium))
|
||||||
|
|
||||||
|
|
||||||
|
large = generate_maze_with_walls(100, 100, 0.3)
|
||||||
|
save_maze_to_file(large, "maze_large.txt")
|
||||||
|
mazes.append(('большой (100x100)', large))
|
||||||
|
|
||||||
|
|
||||||
|
empty = generate_empty_maze(50, 50)
|
||||||
|
save_maze_to_file(empty, "maze_empty.txt")
|
||||||
|
mazes.append(('пустой (50x50)', empty))
|
||||||
|
|
||||||
|
|
||||||
|
no_exit = generate_maze_no_exit(20, 20)
|
||||||
|
save_maze_to_file(no_exit, "maze_no_exit.txt")
|
||||||
|
mazes.append(('без выхода (20x20)', no_exit))
|
||||||
|
|
||||||
|
return mazes
|
||||||
|
|
||||||
|
def run_experiment(maze, strategy, name, repeats=5):
|
||||||
|
|
||||||
|
times = []
|
||||||
|
visited_counts = []
|
||||||
|
path_lengths = []
|
||||||
|
|
||||||
|
for _ in range(repeats):
|
||||||
|
solver = MazeSolver(maze)
|
||||||
|
solver.setStrategy(strategy())
|
||||||
|
|
||||||
|
start_time = time.perf_counter()
|
||||||
|
path, stats = solver.solve()
|
||||||
|
end_time = time.perf_counter()
|
||||||
|
|
||||||
|
times.append((end_time - start_time) * 1000)
|
||||||
|
visited_counts.append(len(path) if path else 0)
|
||||||
|
path_lengths.append(len(path) if path else 0)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'лабиринт': name,
|
||||||
|
'стратегия': strategy.__name__.replace('Strategy', ''),
|
||||||
|
'время_ср': sum(times) / repeats,
|
||||||
|
'время_мин': min(times),
|
||||||
|
'время_макс': max(times),
|
||||||
|
'посещено_ср': sum(visited_counts) / repeats,
|
||||||
|
'длина_пути_ср': sum(path_lengths) / repeats,
|
||||||
|
'путь_найден': path is not None and len(path) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def run_all_experiments():
|
||||||
|
|
||||||
|
strategies = [BFSStrategy, DFSStrategy, AStrategy]
|
||||||
|
results = []
|
||||||
|
|
||||||
|
|
||||||
|
mazes = create_test_mazes()
|
||||||
|
|
||||||
|
for maze_name, maze in mazes:
|
||||||
|
|
||||||
|
|
||||||
|
for strategy in strategies:
|
||||||
|
print(f" тест {strategy.__name__}...", end=" ", flush=True)
|
||||||
|
result = run_experiment(maze, strategy, maze_name)
|
||||||
|
results.append(result)
|
||||||
|
print(f"время={result['время_ср']:.2f}мс, путь={result['длина_пути_ср']:.0f}")
|
||||||
|
|
||||||
|
|
||||||
|
save_results_to_csv(results)
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
def save_results_to_csv(results):
|
||||||
|
|
||||||
|
filename = "resultslab.csv"
|
||||||
|
|
||||||
|
with open(filename, 'w', newline='', encoding='utf-8-sig') as f:
|
||||||
|
writer = csv.DictWriter(f, fieldnames=[
|
||||||
|
'лабиринт', 'стратегия', 'время_ср', 'время_мин', 'время_макс',
|
||||||
|
'посещено_ср', 'длина_пути_ср', 'путь_найден'
|
||||||
|
])
|
||||||
|
writer.writeheader()
|
||||||
|
writer.writerows(results)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def plot_results(results):
|
||||||
|
try:
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
labyrinths = list(set(r['лабиринт'] for r in results))
|
||||||
|
strategies = ['BFS', 'DFS', 'A']
|
||||||
|
|
||||||
|
|
||||||
|
n_rows = 3
|
||||||
|
n_cols = 2
|
||||||
|
fig, axes = plt.subplots(n_rows, n_cols, figsize=(14, 12))
|
||||||
|
axes = axes.flatten()
|
||||||
|
|
||||||
|
for idx, lab in enumerate(labyrinths):
|
||||||
|
ax = axes[idx]
|
||||||
|
|
||||||
|
times = []
|
||||||
|
for strat in strategies:
|
||||||
|
for r in results:
|
||||||
|
if r['лабиринт'] == lab and r['стратегия'] == strat:
|
||||||
|
times.append(r['время_ср'])
|
||||||
|
break
|
||||||
|
|
||||||
|
x = np.arange(len(strategies))
|
||||||
|
bars = ax.bar(x, times, color=['#1a5632', '#0e5fb4', '#051f45'])
|
||||||
|
ax.set_title(f'{lab}')
|
||||||
|
ax.set_xticks(x)
|
||||||
|
ax.set_xticklabels(strategies)
|
||||||
|
ax.set_ylabel('Время (мс)')
|
||||||
|
|
||||||
|
for bar, t in zip(bars, times):
|
||||||
|
ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.5,
|
||||||
|
f'{t:.1f}', ha='center', va='bottom', fontsize=8)
|
||||||
|
|
||||||
|
|
||||||
|
if len(labyrinths) < len(axes):
|
||||||
|
axes[-1].set_visible(False)
|
||||||
|
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig('maze_time_comparison.png', dpi=150)
|
||||||
|
plt.show()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
plt.figure(figsize=(10, 6))
|
||||||
|
|
||||||
|
colors = ['#d8d262', '#0e5fb4', '#ed254e']
|
||||||
|
|
||||||
|
for idx, strat in enumerate(strategies):
|
||||||
|
lengths = []
|
||||||
|
for lab in labyrinths:
|
||||||
|
for r in results:
|
||||||
|
if r['лабиринт'] == lab and r['стратегия'] == strat:
|
||||||
|
lengths.append(r['длина_пути_ср'])
|
||||||
|
break
|
||||||
|
|
||||||
|
plt.plot(labyrinths, lengths, marker='o', label=strat, color=colors[idx]) # добавьте color
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
plt.xlabel('Лабиринт')
|
||||||
|
plt.ylabel('Длина пути ')
|
||||||
|
plt.title('Сравнение длины найденного пути')
|
||||||
|
plt.legend()
|
||||||
|
plt.xticks(rotation=45)
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig('maze_path_length.png', dpi=150)
|
||||||
|
plt.show()
|
||||||
|
|
||||||
|
except ImportError:
|
||||||
|
print("")
|
||||||
|
|
||||||
|
|
||||||
|
def print_analysis(results):
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
strat_data = {}
|
||||||
|
for r in results:
|
||||||
|
strat = r['стратегия']
|
||||||
|
if strat not in strat_data:
|
||||||
|
strat_data[strat] = {'time': [], 'visited': [], 'labyrinth': []}
|
||||||
|
strat_data[strat]['time'].append(r['время_ср'])
|
||||||
|
strat_data[strat]['visited'].append(r['посещено_ср'])
|
||||||
|
strat_data[strat]['labyrinth'].append(r['лабиринт'])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
for strat, data in strat_data.items():
|
||||||
|
avg_time = sum(data['time']) / len(data['time'])
|
||||||
|
print(f" {strat}: среднее время {avg_time:.2f} мс")
|
||||||
|
|
||||||
|
|
||||||
|
print(" BFS медленный на большом лабсамый короткий путить находит")
|
||||||
|
print(" DFS быстрый, но не всегда самый короткий")
|
||||||
|
print(" A быстрый и находит самый короткий путь")
|
||||||
|
print(" без выхода лаб. стратегии самые медленные ")
|
||||||
|
print(" в пустом стратегии самые быстрые")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
|
||||||
|
results = run_all_experiments()
|
||||||
|
|
||||||
|
|
||||||
|
print_analysis(results)
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
plot_results(results)
|
||||||
|
except:
|
||||||
|
print("")
|
||||||
50
BorisovMI/lab_2/docs/data/maze_empty.txt
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
s
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
e
|
||||||
100
BorisovMI/lab_2/docs/data/maze_large.txt
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
s## # # # # # # # ### # ## # # # # # # ### # # # # # ###
|
||||||
|
# ## # ## ### ## # # ## ## # # # ### ## ## #
|
||||||
|
# ## # # # # # ## ## # # # ## # ## # # # # ## # # # #
|
||||||
|
## # # # ##### # # # # # ## # ## # # # # # # # # # # # # #
|
||||||
|
# ### ## # # # # ## ## ## # # # # # ## #
|
||||||
|
# # # # # # ## # ### ## ## # ##### # # # # # ### ## # ### # # #
|
||||||
|
# # # ## ### # # # # # ### # # # ## ##### # #
|
||||||
|
# # ### # # ## ### # # # # # ## # ## ## # ## # ## ## # #
|
||||||
|
# # # ## # # # # ## # # # # # # # # # # # ### ## # # #
|
||||||
|
# # # # # ## # # # # # ## ## ### ###### ## ## ### #
|
||||||
|
## ## # # # # # # ### # # # ### # ## #
|
||||||
|
# # ###### # # # ## # # # ## # # # ## #### # # #
|
||||||
|
## # # # ### # # # # # #### # # # ## # # # #
|
||||||
|
# # # # ### ## ## # # # ## # # # # # ### # # # ### # # #### # ##
|
||||||
|
# ## # # # # # # # # # # # # # # ## ### ## # # ## #
|
||||||
|
### # # # # ## ### # # ## # # # ## # ## # ## #
|
||||||
|
### # # # # ### # # # # # ## # # # # # ## # # ## #
|
||||||
|
# # # # ## # # ### # ## ## # ### # # ### ## #
|
||||||
|
### ## # ## # ## # # # # # # # # # # # ####### ##
|
||||||
|
## ## # # # # ## # # # ## ### ### # # # ### # # # # ## # ###
|
||||||
|
### #### ### # # # # ## ## # #### # # # # # # ## # #
|
||||||
|
### # ## ## # ## ## ## # # # ## # # ## # ## # #
|
||||||
|
# # # # # # # #### ## # # #### ## # ## ## # # #
|
||||||
|
# ## # ## # # # # ### # ## # ## # # ## # # # ## # # #### # #
|
||||||
|
# # ## # # # # # # # # # ## ## # # # ### # #
|
||||||
|
# # # # # # ## # # # ### # ## # # # # ## # # # # # #
|
||||||
|
# # # ### # # # # # ## ## # # ## # # ## # # #
|
||||||
|
## ## ### # # ## # # # # # # # # # # # # # ## ## # # # #
|
||||||
|
# # # # # # ## # # # # # # # ##
|
||||||
|
# # # # # # ## # ## # ## # # # ## ## ## ## ### # # # # # #
|
||||||
|
# # # # # # # #### # ## # # # # ## ## # # # ## # #
|
||||||
|
## # # # # # # # ###### # # ### # # ## # # # # ### ##
|
||||||
|
# # ## # # # # #### # #### # # # ## ## ## #
|
||||||
|
# # # # # # # ## # # # # # ### ### # # # # # # #
|
||||||
|
# # # # ## # # # # # ## # ## # # ## # ## ### # #
|
||||||
|
#### # # # # ## # # # # # ## ### # # # # ### # ## #
|
||||||
|
# # # # ## ## # # # # # # # # # # # # # # # ## ## # # ##
|
||||||
|
# # # # # # ## # # # ## ## # # # # # # ## #
|
||||||
|
# # ## ## ### ## # # ## # # # ## # # # # # # # # #
|
||||||
|
## ## # # # # # # ## ### # # # ## # # ## # ### # ### ##
|
||||||
|
## ## # # # # # # # # # ## # # ## # ### # # # #
|
||||||
|
## # # ## ## ## # # ## # # ## # # # # ## # #
|
||||||
|
## # ## # ## ## # # # # # # # # # # ### # # # # # ## # #
|
||||||
|
# ## ## # # # # # #### ## # # # # # # # # #
|
||||||
|
# # # # # # # # ## # # # # # ### # # # # #### ## ### ####
|
||||||
|
# ## # # #### # # # # #### # # # # # ### # # ### # ## ##
|
||||||
|
## # ## # ## # # # # # # ### # # # # # ## # # #
|
||||||
|
# # # # ## # # # ### #### ## # # # # ## ## ## #
|
||||||
|
## # ## # ## # # # # ## # # # # # # # # # # # ## #
|
||||||
|
# # ## # # # ### # ## # ## # # ### # # # # ### #
|
||||||
|
# # ## # ## #### # # # # # # # ## ## # ## ###
|
||||||
|
# ## # # ## ## # # ## # # # ### # ## # # # # # # # #
|
||||||
|
# # ## # # ## # # # # # # # # # # # ## # ### ##
|
||||||
|
# ## # # # # # # # ## # # ## ## ## # # ## ## # # ## ### ### ####
|
||||||
|
### # # # # # # # ## # # # ## # ## # # # ## # # ## # # # #
|
||||||
|
# # # # # # # # ## ## ### # # # # # # ## # # # #
|
||||||
|
# # # # ## # ### # # # # ## # # # ### # ## ## # # # ##
|
||||||
|
# # # # # ## # ## # # # ### # ## ## # # # # # # # #
|
||||||
|
## ### ## # # # # ## # # # #### # #### # # ## # ## #
|
||||||
|
## ## ## # # # # ## # # ## ## ### # # # # # ### # ### ##
|
||||||
|
# # ### # # # # # # # # # ## # ### # # # ### ## ##
|
||||||
|
# # ## # ## # ## ## # # # ## ## # ## # # ##
|
||||||
|
# # ### # ## ## # # ### # # # # # # ## ## # ##
|
||||||
|
# # #### # # # # # # # ### # # # # # # ## # ### # # ### ###
|
||||||
|
# # ## # # ##### # ## # # ## ## # # # ## # # # ## ##
|
||||||
|
# ### # ## # # ###### ### # ## # ## # # ## # # # # ## ## # ## #
|
||||||
|
# # # # # # # # ## ## # # # ## # # ## ## # # # # #
|
||||||
|
## # ## ## # ### # # # # # # # # ## # # # # # ###### # ##
|
||||||
|
## # # # # ### # # ### ## # # ## # # # # ##### #
|
||||||
|
# # ### # # # # # # ## #### # # ### # # # ## # ##
|
||||||
|
# # ## ## # ## # #### # ## # # # # # ## ## # # # # ## ## #
|
||||||
|
## # # # # ## # # ## # # # ## # # ## # # # # # #
|
||||||
|
# # # ## # # # # ## # ## # # # # # ## # # ##
|
||||||
|
# ## ## # # # # # # ### # ## # # # # # # # # #
|
||||||
|
# # ## # # # # # # # ##### ## ## ### # # ###
|
||||||
|
# # # # # # ## ## ## # # # # # # ## # ##### # ##
|
||||||
|
# # ## # # # ## # # #### # ## # # # # # ## # # #
|
||||||
|
# # # # # ## ## # ## # # # # # #### # ##
|
||||||
|
## # # # # ## # ## ## ## # # ## # # # ## # ## # # #
|
||||||
|
## # # # # # # # ## ### # # # ## # # ## #
|
||||||
|
### # ## # # # ## ## # ### # # # # # ### # # # ##### #
|
||||||
|
## # # ## # ## # # # # # ## # # # ## ####### ### # #
|
||||||
|
#### # # # # # # # # # # ## # ## # # ### # ## # # #
|
||||||
|
# # # # # # # # # # ## # # ## # # # # ## # ### # #
|
||||||
|
# # # # #### ## ## # # # # ## # # # # # # ### ### # ##
|
||||||
|
#### # ## # # # ### ## # ## ## # ## # # ## # #
|
||||||
|
# # ## # # # # # # # # ## # # ## # # ### # ##
|
||||||
|
# # # # ## ## # # ## # # # # ## # ## ##
|
||||||
|
### ## # # # ## ## ## ## # # # ## ## # # # # # # # # # ## # # #
|
||||||
|
## # # # # # # # # # # ## #### # # ## ### ### ## # # #
|
||||||
|
# # ##### # # # ## ## # # ## ## # # ## # #### ##### # # ## ##
|
||||||
|
# # # # # # ## # # # # # # # # # # ## #
|
||||||
|
## ### # # ## ## # ## ## ## # # ## # # ### # # ## ### #
|
||||||
|
# # # ## # ## # # # ## # # # # ## # # # #
|
||||||
|
# # # # # #### # # # ## # # # ## # # # # # # # # # #
|
||||||
|
# # # ## # # ## # # ### # # ## # # ## # # ##
|
||||||
|
# # # ## # # ### # # # # # ## ## ##
|
||||||
|
# # # # ### # # # # # # # # # # # ## ## # ### # ## # # # #
|
||||||
|
# ###### # # ## ## ## # ### # # # ## # # # #####
|
||||||
|
# ## # # # # ## # # # # # # # # #### # # e
|
||||||
50
BorisovMI/lab_2/docs/data/maze_medium.txt
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
s # ## # # ### # ## # # #
|
||||||
|
## # # ## ## # # # #
|
||||||
|
# # ## # # # # ##
|
||||||
|
### # # # # # # ## ## # ## # #
|
||||||
|
# # # ## # # # # ## # #
|
||||||
|
# # # # ## # ## # # #
|
||||||
|
## # # # # # # # ## # #
|
||||||
|
# ## # # # ## # ## # # # # #
|
||||||
|
## # # # # ## # # ## # ##
|
||||||
|
# # # # # ## # # ## # # #
|
||||||
|
# # # # ## # # # # ## # ## # #
|
||||||
|
# ## # # # # # # # ## ##
|
||||||
|
## # ## ### # # # ## # ##
|
||||||
|
##### ### # # # # ## # # # #
|
||||||
|
# # ### ## # ## ## #### ###
|
||||||
|
## # # # # ### # # ## # #
|
||||||
|
# # ## # # # # # # ##
|
||||||
|
## # # # ### # ## # # ## # # ## ##
|
||||||
|
# #### # # # # # ### # ##
|
||||||
|
# ## # ## # # ## ### ## ### #
|
||||||
|
# # ### ## # # # ##
|
||||||
|
# # ## # # # # # # #
|
||||||
|
# ## # ### #### # ## # ### ## # #
|
||||||
|
# # ## # # # # # # #
|
||||||
|
# # ##### # # # # # # # ## # ##
|
||||||
|
## # # # # ## ## # ## ## #
|
||||||
|
# # # # # # # ## # # #
|
||||||
|
## # # # ## # # ## # #
|
||||||
|
# ### # # # # # # # # # ###
|
||||||
|
### # # # # # ### # # # # # ##
|
||||||
|
# # # # # ## # # # # # ##
|
||||||
|
# ## ## ## # # # # # # ## #
|
||||||
|
# #### # # # ## # ## #
|
||||||
|
## # # # # ## # # # # #
|
||||||
|
## # ## ## # # # ## # # ## #
|
||||||
|
# # # # # # # # # # ### # # #
|
||||||
|
# # ## # # # # # ###
|
||||||
|
# # #### ##
|
||||||
|
# # ## # # ## ### # # ##
|
||||||
|
##### # # # # # # # # # #
|
||||||
|
## # # # # # #
|
||||||
|
# # ## ## # # # # ## ### # #
|
||||||
|
# # ### ## ### ### # ## # #
|
||||||
|
## # ### # ## # # # #
|
||||||
|
# # # # # ## # # # # #
|
||||||
|
# # ## # # ## ### # # # #
|
||||||
|
# # # # # ## # ### #
|
||||||
|
## # # ## # # #
|
||||||
|
# # ## # ### # ### # ## # ## # ##
|
||||||
|
# # # # # # # ## # # e
|
||||||
20
BorisovMI/lab_2/docs/data/maze_no_exit.txt
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
s ## ###
|
||||||
|
# # # # # ##
|
||||||
|
# # # # #
|
||||||
|
# # ##
|
||||||
|
# # # # #
|
||||||
|
# # ### # #
|
||||||
|
# # # # #
|
||||||
|
# # ## ## ###
|
||||||
|
# ## #
|
||||||
|
# # ###
|
||||||
|
# # # # #
|
||||||
|
### # #
|
||||||
|
# # # #
|
||||||
|
## # # # #
|
||||||
|
## # # # # ##
|
||||||
|
# # #
|
||||||
|
# #
|
||||||
|
# # # #
|
||||||
|
# # #
|
||||||
|
# # # # ## #
|
||||||
BIN
BorisovMI/lab_2/docs/data/maze_path_length.png
Normal file
|
After Width: | Height: | Size: 101 KiB |
10
BorisovMI/lab_2/docs/data/maze_small.txt
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
s #
|
||||||
|
|
||||||
|
#
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
#
|
||||||
|
# #
|
||||||
|
#
|
||||||
|
# # e
|
||||||
BIN
BorisovMI/lab_2/docs/data/maze_time_comparison.png
Normal file
|
After Width: | Height: | Size: 103 KiB |
252
BorisovMI/lab_2/docs/data/report2.md
Normal file
|
|
@ -0,0 +1,252 @@
|
||||||
|
# Отчёт: Задание 2 — Поиск выхода из лабиринта
|
||||||
|
|
||||||
|
## Цель работы
|
||||||
|
|
||||||
|
Разработать гибкую, расширяемую программу для загрузки лабиринта из файла, поиска пути от старта до выхода с возможностью выбора алгоритма, визуализации процесса и экспериментального сравнения алгоритмов
|
||||||
|
|
||||||
|
## Выбранные паттерны и их обоснование
|
||||||
|
|
||||||
|
### Builder
|
||||||
|
|
||||||
|
Для загрузки лабиринта из файла был использован паттерн Builder.
|
||||||
|
Создан интерфейс:
|
||||||
|
class MazeBuilder():
|
||||||
|
и его реализация:
|
||||||
|
class TextFileMazeBuilder(MazeBuilder):
|
||||||
|
Преимущества использования Builder:
|
||||||
|
пользоватеь не знает деталей создания лабиринта;
|
||||||
|
можно добавить новые форматы (JSON, XML, бинарный);
|
||||||
|
код загрузки изолирован от остальной программы.
|
||||||
|
|
||||||
|
### Strategy
|
||||||
|
|
||||||
|
Для алгоритмов поиска пути использован паттерн Strategy
|
||||||
|
Создан общий интерфейс:
|
||||||
|
class PathFindingStrategy():
|
||||||
|
Реализованы стратегии:
|
||||||
|
BFSStrategy;
|
||||||
|
DFSStrategy;
|
||||||
|
AStrategy;
|
||||||
|
Каждая стратегия реализует собственный алгоритм поиска пути по правилам.
|
||||||
|
Преимущества паттерна:
|
||||||
|
алгоритмы можно менять во время выполнения;
|
||||||
|
код MazeSolver не зависит от конкретного алгоритма;
|
||||||
|
новые алгоритмы можно добавлять без изменения существующего кода.
|
||||||
|
|
||||||
|
### Observer
|
||||||
|
|
||||||
|
Для уведомления интерфейса о событиях использован паттерн Observer
|
||||||
|
Создан интерфейс:
|
||||||
|
class Observer():
|
||||||
|
и реализация:
|
||||||
|
class ConsoleView(Observer):
|
||||||
|
MazeSolver хранит список наблюдателей и уведомляет их о событиях:
|
||||||
|
начало поиска;
|
||||||
|
окончание поиска;
|
||||||
|
перемещение игрока.
|
||||||
|
Преимущества:
|
||||||
|
логика интерфейса отделена от логики поиска;
|
||||||
|
можно легко добавить графический интерфейс;
|
||||||
|
|
||||||
|
### Command
|
||||||
|
|
||||||
|
Для пошагового перемещения игрока использован паттерн Command.
|
||||||
|
Создан интерфейс:
|
||||||
|
class Command():
|
||||||
|
и реализация:
|
||||||
|
class MoveCommand(Command):
|
||||||
|
Каждая команда умеет:
|
||||||
|
execute() — выполнить действие;
|
||||||
|
undo() — отменить действие
|
||||||
|
Преимущества:
|
||||||
|
поддержка undo;
|
||||||
|
возможность расширения системы команд
|
||||||
|
|
||||||
|
## Листинги ключевых классов
|
||||||
|
|
||||||
|
### Паттерн Strategy
|
||||||
|
|
||||||
|
class PathFindingStrategy:
|
||||||
|
def findPath(self, maze, start, exit):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class BFSStrategy(PathFindingStrategy):
|
||||||
|
def findPath(self, maze, start, exit):
|
||||||
|
if exit is None:
|
||||||
|
return []
|
||||||
|
queue = deque([start])
|
||||||
|
visited = {start}
|
||||||
|
parent = {start: None}
|
||||||
|
|
||||||
|
while queue:
|
||||||
|
current = queue.popleft()
|
||||||
|
|
||||||
|
if current == exit:
|
||||||
|
return self._reconstruct_path(parent, start, exit)
|
||||||
|
|
||||||
|
for neighbor in maze.getNeighbors(current):
|
||||||
|
if neighbor not in visited:
|
||||||
|
visited.add(neighbor)
|
||||||
|
parent[neighbor] = current
|
||||||
|
queue.append(neighbor)
|
||||||
|
|
||||||
|
return []
|
||||||
|
class AStrategy(PathFindingStrategy):
|
||||||
|
def _heuristic(self, cell, exit):
|
||||||
|
if exit is None:
|
||||||
|
return 0
|
||||||
|
return abs(cell.x - exit.x) + abs(cell.y - exit.y)
|
||||||
|
|
||||||
|
def findPath(self, maze, start, exit):
|
||||||
|
if exit is None:
|
||||||
|
return []
|
||||||
|
open_set = []
|
||||||
|
heapq.heappush(open_set, (0, start))
|
||||||
|
|
||||||
|
came_from = {start: None}
|
||||||
|
g_score = {start: 0}
|
||||||
|
|
||||||
|
while open_set:
|
||||||
|
current = heapq.heappop(open_set)[1]
|
||||||
|
|
||||||
|
if current == exit:
|
||||||
|
return self._reconstruct_path(came_from, start, exit)
|
||||||
|
|
||||||
|
for neighbor in maze.getNeighbors(current):
|
||||||
|
tentative_g = g_score[current] + 1
|
||||||
|
|
||||||
|
if neighbor not in g_score or tentative_g < g_score[neighbor]:
|
||||||
|
came_from[neighbor] = current
|
||||||
|
g_score[neighbor] = tentative_g
|
||||||
|
f_score = tentative_g + self._heuristic(neighbor, exit)
|
||||||
|
heapq.heappush(open_set, (f_score, neighbor))
|
||||||
|
|
||||||
|
return []
|
||||||
|
|
||||||
|
### Паттерн Command
|
||||||
|
|
||||||
|
class Command:
|
||||||
|
def execute(self): pass
|
||||||
|
def undo(self): pass
|
||||||
|
|
||||||
|
class MoveCommand(Command):
|
||||||
|
def __init__(self, player, direction, maze):
|
||||||
|
self.player = player
|
||||||
|
self.dx, self.dy = direction
|
||||||
|
self.maze = maze
|
||||||
|
self.executed = False
|
||||||
|
|
||||||
|
def execute(self):
|
||||||
|
new_x = self.player.currentCell.x + self.dx
|
||||||
|
new_y = self.player.currentCell.y + self.dy
|
||||||
|
new_cell = self.maze.getCell(new_x, new_y)
|
||||||
|
if new_cell and new_cell.isPassable():
|
||||||
|
self.player.moveTo(new_cell)
|
||||||
|
self.executed = True
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def undo(self):
|
||||||
|
if self.executed:
|
||||||
|
self.player.undoMove()
|
||||||
|
self.executed = False
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Результаты
|
||||||
|
|
||||||
|
| Лабиринт | Стратегия | Время (с) | Посещено | Длина пути | Путь найден |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| маленький (10x10) | BFS | 0.9148200158961117 | 19.0 | 19.0 | True |
|
||||||
|
| маленький (10x10) | DFS | 0.717819994315505 | 39.0 | 39.0 | True |
|
||||||
|
| маленький (10x10) | A | 1.577159995213151 | 19.0 | 19.0 | True |
|
||||||
|
| средний (50x50) | BFS | 14.496059995144606 | 99.0 | 99.0 | True |
|
||||||
|
| средний (50x50) | DFS | 8.470179990399629 | 393.0 | 393.0 |True |
|
||||||
|
| средний (50x50) | A | 9.11291999509558 | 99.0 | 99.0 | True |
|
||||||
|
| большой (100x100) | BFS | 0.013179995585232973 | 0.0 | 0.0 | False |
|
||||||
|
| большой (100x100) | A | 0.013079994823783636 | 0.0 | 0.0 | False |
|
||||||
|
| пустой (50x50) | BFS | 29.2012800113298 | 99.0 | 99.0 | True |
|
||||||
|
| пустой (50x50) | DFS | 13.176999986171722 | 1275.0 | 1275.0 | True |
|
||||||
|
| пустой (50x50) | A | 50.366899999789894 | 99.0 | 99.0 | True |
|
||||||
|
| без выхода (20x20) | BFS | 0.004239997360855341 | 0.0 | 0.0 | False |
|
||||||
|
| без выхода (20x20) | DFS | 0.006399990525096655 | 0.0 | 0.0 | False |
|
||||||
|
| без выхода (20x20) | A | 0.008680007886141539 | 0.0 | 0.0 | False |
|
||||||
|
|
||||||
|
### Графики
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Анализ эффективности алгоритмов
|
||||||
|
|
||||||
|
В ходе экспериментов были получены следующие результаты.
|
||||||
|
|
||||||
|
### BFS
|
||||||
|
Преимущества:
|
||||||
|
всегда находит кратчайший путь;
|
||||||
|
простая реализация.
|
||||||
|
Недостатки:
|
||||||
|
посещает большое количество клеток;
|
||||||
|
требует много памяти.
|
||||||
|
Выходит, что наиболее эффективен в небольших невзвешенных лабиринтах.
|
||||||
|
|
||||||
|
### DFS
|
||||||
|
Преимущества:
|
||||||
|
простая реализация;
|
||||||
|
самым быстрым находит произвольный путь.
|
||||||
|
Недостатки:
|
||||||
|
не гарантирует кратчайший путь;
|
||||||
|
может уходить в тупики.
|
||||||
|
Подходит для быстрого поиска любого решения.
|
||||||
|
|
||||||
|
### A
|
||||||
|
Преимущества:
|
||||||
|
высокая скорость;
|
||||||
|
посещает меньше клеток;
|
||||||
|
Недостатки:
|
||||||
|
требует выбора хорошей эвристики.
|
||||||
|
Показал хорошие результаты на больших лабиринтах.
|
||||||
|
|
||||||
|
## Анализ применимости паттернов
|
||||||
|
|
||||||
|
### Builder
|
||||||
|
Без Builder код загрузки лабиринта был бы жёстко связан с классом Maze, а добавление нового формата потребовало бы изменения существующего кода.
|
||||||
|
Strategy
|
||||||
|
Без Strategy пришлось бы:
|
||||||
|
хранить все алгоритмы внутри одного класса;
|
||||||
|
использовать большое количество условных операторов;
|
||||||
|
изменять код MazeSolver при добавлении новых алгоритмов
|
||||||
|
Strategy помог полностью отделить алгоритмы друг от друга.
|
||||||
|
|
||||||
|
### Observer
|
||||||
|
Без Observer логика интерфейса смешивалась бы с логикой поиска.
|
||||||
|
Это усложнило бы:
|
||||||
|
добавление GUI;
|
||||||
|
логирование;
|
||||||
|
визуализацию.
|
||||||
|
|
||||||
|
### Command
|
||||||
|
Без Command было бы сложно реализовать:
|
||||||
|
undo;
|
||||||
|
историю действий;
|
||||||
|
расширяемую систему управления.
|
||||||
|
|
||||||
|
## Выводы
|
||||||
|
|
||||||
|
### В проекте были успешно реализованы:
|
||||||
|
загрузка лабиринта из файла;
|
||||||
|
несколько алгоритмов поиска пути;
|
||||||
|
визуализация;
|
||||||
|
система наблюдателей;
|
||||||
|
система команд;
|
||||||
|
экспериментальное сравнение алгоритмов.
|
||||||
|
|
||||||
|
### Использование паттернов GoF позволило:
|
||||||
|
сделать архитектуру гибкой;
|
||||||
|
уменьшить связанность компонентов;
|
||||||
|
упростить расширение программы;
|
||||||
|
облегчить сопровождение кода.
|
||||||
16
BorisovMI/lab_2/docs/data/resultslab.csv
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
лабиринт,стратегия,время_ср,время_мин,время_макс,посещено_ср,длина_пути_ср,путь_найден
|
||||||
|
маленький (10x10),BFS,0.9148200158961117,0.8840999798849225,0.9673000313341618,19.0,19.0,True
|
||||||
|
маленький (10x10),DFS,0.717819994315505,0.5779999773949385,0.8650000090710819,39.0,39.0,True
|
||||||
|
маленький (10x10),A,1.577159995213151,1.531599962618202,1.7019000370055437,19.0,19.0,True
|
||||||
|
средний (50x50),BFS,14.496059995144606,12.946999981068075,18.392199999652803,99.0,99.0,True
|
||||||
|
средний (50x50),DFS,8.470179990399629,7.544599997345358,9.55930002965033,393.0,393.0,True
|
||||||
|
средний (50x50),A,9.11291999509558,8.53859999915585,9.788900031708181,99.0,99.0,True
|
||||||
|
большой (100x100),BFS,0.013179995585232973,0.009100011084228754,0.026200024876743555,0.0,0.0,False
|
||||||
|
большой (100x100),DFS,0.012619991321116686,0.008300004992634058,0.026499968953430653,0.0,0.0,False
|
||||||
|
большой (100x100),A,0.013079994823783636,0.008699949830770493,0.027500034775584936,0.0,0.0,False
|
||||||
|
пустой (50x50),BFS,29.2012800113298,19.71900003263727,47.252200020011514,99.0,99.0,True
|
||||||
|
пустой (50x50),DFS,13.176999986171722,12.441499973647296,13.887099979911,1275.0,1275.0,True
|
||||||
|
пустой (50x50),A,50.366899999789894,47.1535999677144,60.296199982985854,99.0,99.0,True
|
||||||
|
без выхода (20x20),BFS,0.004239997360855341,0.002700020559132099,0.00909995287656784,0.0,0.0,False
|
||||||
|
без выхода (20x20),DFS,0.006399990525096655,0.003200024366378784,0.012699980288743973,0.0,0.0,False
|
||||||
|
без выхода (20x20),A,0.008680007886141539,0.005399982910603285,0.01810002140700817,0.0,0.0,False
|
||||||
|
BIN
BrychkinKA/427.md
Normal file
47
BrychkinKA/docs/class_diagram.mmd
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
classDiagram
|
||||||
|
class Maze {
|
||||||
|
+width
|
||||||
|
+height
|
||||||
|
+cells
|
||||||
|
+start
|
||||||
|
+exit
|
||||||
|
+get_neighbors()
|
||||||
|
}
|
||||||
|
|
||||||
|
class Cell {
|
||||||
|
+x
|
||||||
|
+y
|
||||||
|
+is_wall
|
||||||
|
+is_start
|
||||||
|
+is_exit
|
||||||
|
}
|
||||||
|
|
||||||
|
class MazeBuilder {
|
||||||
|
<<interface>>
|
||||||
|
+build_from_file()
|
||||||
|
}
|
||||||
|
|
||||||
|
class TextFileMazeBuilder {
|
||||||
|
+build_from_file()
|
||||||
|
}
|
||||||
|
|
||||||
|
class PathFindingStrategy {
|
||||||
|
<<interface>>
|
||||||
|
+find_path()
|
||||||
|
}
|
||||||
|
|
||||||
|
class BFSStrategy
|
||||||
|
class DFSStrategy
|
||||||
|
class AStarStrategy
|
||||||
|
|
||||||
|
class MazeSolver {
|
||||||
|
+solve()
|
||||||
|
}
|
||||||
|
|
||||||
|
Maze --> Cell
|
||||||
|
TextFileMazeBuilder ..|> MazeBuilder
|
||||||
|
BFSStrategy ..|> PathFindingStrategy
|
||||||
|
DFSStrategy ..|> PathFindingStrategy
|
||||||
|
AStarStrategy ..|> PathFindingStrategy
|
||||||
|
MazeSolver --> PathFindingStrategy
|
||||||
|
MazeSolver --> Maze
|
||||||
135
BrychkinKA/docs/conclusion.md
Normal file
|
|
@ -0,0 +1,135 @@
|
||||||
|
# Отчёт по заданию №2
|
||||||
|
|
||||||
|
### Реализация поиска пути в лабиринте с использованием паттернов проектирования
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Цель работы
|
||||||
|
|
||||||
|
Разработать архитектуру и реализацию системы поиска пути в лабиринте, применив паттерны:
|
||||||
|
|
||||||
|
- Builder — построение лабиринта из файла
|
||||||
|
- Strategy — выбор алгоритма поиска
|
||||||
|
- Observer — отображение состояния
|
||||||
|
- Command — управление игроком
|
||||||
|
|
||||||
|
Также провести экспериментальное сравнение алгоритмов BFS, DFS и A\*.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Архитектура проекта
|
||||||
|
|
||||||
|
Структура каталогов:
|
||||||
|
|
||||||
|
```
|
||||||
|
BrychkinKA/
|
||||||
|
│
|
||||||
|
├── src/
|
||||||
|
│ ├── builder/
|
||||||
|
│ ├── model/
|
||||||
|
│ ├── solver/
|
||||||
|
│ ├── strategy/
|
||||||
|
│ └── ui/
|
||||||
|
│
|
||||||
|
├── mazes/
|
||||||
|
├── experiments/
|
||||||
|
└── docs/
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Используемые паттерны
|
||||||
|
|
||||||
|
### 3.1 Builder
|
||||||
|
|
||||||
|
Абстрагирует процесс построения лабиринта из текстового файла.
|
||||||
|
|
||||||
|
### 3.2 Strategy
|
||||||
|
|
||||||
|
Позволяет переключать алгоритмы поиска пути без изменения остального кода.
|
||||||
|
|
||||||
|
### 3.3 Observer
|
||||||
|
|
||||||
|
Используется для отображения состояния лабиринта в консоли.
|
||||||
|
|
||||||
|
### 3.4 Command
|
||||||
|
|
||||||
|
Реализует управление игроком и пошаговое перемещение.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Диаграмма классов
|
||||||
|
|
||||||
|
Диаграмма находится в файле: `class_diagram.mmd`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Эксперименты
|
||||||
|
|
||||||
|
Эксперименты проводились на пяти лабиринтах:
|
||||||
|
|
||||||
|
- small.txt — простой, проходимый
|
||||||
|
- medium.txt — средний по сложности
|
||||||
|
- empty.txt — полностью свободное поле
|
||||||
|
- no_exit.txt — отсутствует выход
|
||||||
|
- big.txt — большой лабиринт, путь отсутствует
|
||||||
|
|
||||||
|
Алгоритмы:
|
||||||
|
|
||||||
|
- BFS
|
||||||
|
- DFS
|
||||||
|
- A\*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Результаты
|
||||||
|
|
||||||
|
### 6.1 Таблица результатов
|
||||||
|
|
||||||
|
| Файл | Алгоритм | Посещено | Длина пути |
|
||||||
|
| ----------- | -------- | -------- | ---------- |
|
||||||
|
| big.txt | BFS | 27 | 0 |
|
||||||
|
| big.txt | DFS | 27 | 0 |
|
||||||
|
| big.txt | A\* | 27 | 0 |
|
||||||
|
| empty.txt | BFS | 10 | 10 |
|
||||||
|
| empty.txt | DFS | 10 | 10 |
|
||||||
|
| empty.txt | A\* | 10 | 10 |
|
||||||
|
| medium.txt | BFS | 21 | 17 |
|
||||||
|
| medium.txt | DFS | 19 | 17 |
|
||||||
|
| medium.txt | A\* | 21 | 17 |
|
||||||
|
| no_exit.txt | BFS | 0 | 0 |
|
||||||
|
| no_exit.txt | DFS | 0 | 0 |
|
||||||
|
| no_exit.txt | A\* | 0 | 0 |
|
||||||
|
| small.txt | BFS | 7 | 7 |
|
||||||
|
| small.txt | DFS | 7 | 7 |
|
||||||
|
| small.txt | A\* | 7 | 7 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Графики
|
||||||
|
|
||||||
|
Графики находятся в файле:
|
||||||
|
|
||||||
|
`experiments/plot_graphs.py`
|
||||||
|
|
||||||
|
- время работы алгоритмов
|
||||||
|
- количество посещённых клеток
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Выводы
|
||||||
|
|
||||||
|
1. A\* показывает лучшие результаты на средних и больших лабиринтах, но имеет небольшой накладной расход.
|
||||||
|
2. DFS посещает меньше клеток, но не гарантирует кратчайший путь.
|
||||||
|
3. BFS всегда находит кратчайший путь, но исследует больше пространства.
|
||||||
|
4. На лабиринтах без выхода все алгоритмы корректно возвращают `path_len = 0`.
|
||||||
|
5. Архитектура с паттернами позволяет легко расширять проект и добавлять новые алгоритмы.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Приложения
|
||||||
|
|
||||||
|
- Исходный код
|
||||||
|
- Лабиринты
|
||||||
|
- CSV с результатами
|
||||||
|
- Диаграммы
|
||||||
21
BrychkinKA/docs/diagrams.md
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
# Диаграммы проекта
|
||||||
|
|
||||||
|
## 1. Диаграмма классов
|
||||||
|
|
||||||
|
См. файл `class_diagram.mmd`.
|
||||||
|
|
||||||
|
## 2. Структура каталогов
|
||||||
|
|
||||||
|
```
|
||||||
|
vinichukan/
|
||||||
|
├── src/
|
||||||
|
├── mazes/
|
||||||
|
├── experiments/
|
||||||
|
└── docs/
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. Логика работы алгоритмов
|
||||||
|
|
||||||
|
- BFS — поиск в ширину
|
||||||
|
- DFS — поиск в глубину
|
||||||
|
- A\* — эвристический поиск с манхэттенской метрикой
|
||||||
65
BrychkinKA/experiments/benchmark.py
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import csv
|
||||||
|
from time import perf_counter
|
||||||
|
|
||||||
|
# Добавляем корневую папку BrychkinKA в sys.path
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
from src.builder.text_file_maze_builder import TextFileMazeBuilder
|
||||||
|
from src.strategy.bfs_strategy import BFSStrategy
|
||||||
|
from src.strategy.dfs_strategy import DFSStrategy
|
||||||
|
from src.strategy.astar_strategy import AStarStrategy
|
||||||
|
from src.solver.maze_solver import MazeSolver
|
||||||
|
|
||||||
|
|
||||||
|
def run_experiments():
|
||||||
|
builder = TextFileMazeBuilder()
|
||||||
|
|
||||||
|
strategies = {
|
||||||
|
"BFS": BFSStrategy(),
|
||||||
|
"DFS": DFSStrategy(),
|
||||||
|
"A*": AStarStrategy()
|
||||||
|
}
|
||||||
|
|
||||||
|
# Папка с лабиринтами относительно корня
|
||||||
|
root_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
maze_dir = os.path.join(root_dir, "mazes")
|
||||||
|
|
||||||
|
files = [f for f in os.listdir(maze_dir) if f.endswith(".txt")]
|
||||||
|
|
||||||
|
results = []
|
||||||
|
|
||||||
|
for maze_file in files:
|
||||||
|
maze_path = os.path.join(maze_dir, maze_file)
|
||||||
|
maze = builder.build_from_file(maze_path)
|
||||||
|
|
||||||
|
for name, strategy in strategies.items():
|
||||||
|
solver = MazeSolver(maze, strategy)
|
||||||
|
|
||||||
|
t0 = perf_counter()
|
||||||
|
stats = solver.solve()
|
||||||
|
t1 = perf_counter()
|
||||||
|
|
||||||
|
results.append([
|
||||||
|
maze_file,
|
||||||
|
name,
|
||||||
|
stats.time_ms,
|
||||||
|
stats.visited,
|
||||||
|
stats.path_len
|
||||||
|
])
|
||||||
|
|
||||||
|
print(f"{maze_file} | {name} | {stats}")
|
||||||
|
|
||||||
|
# Сохраняем results.csv в папку experiments
|
||||||
|
output_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "results.csv")
|
||||||
|
with open(output_path, "w", newline="", encoding="utf-8") as f:
|
||||||
|
writer = csv.writer(f)
|
||||||
|
writer.writerow(["maze", "algorithm", "time_ms", "visited", "path_len"])
|
||||||
|
writer.writerows(results)
|
||||||
|
|
||||||
|
print(f"\nРезультаты сохранены в {output_path}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
run_experiments()
|
||||||
76
BrychkinKA/experiments/plot_graphs.py
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
import csv
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import os
|
||||||
|
|
||||||
|
def plot_results():
|
||||||
|
# Определяем правильный путь к results.csv
|
||||||
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
csv_path = os.path.join(script_dir, "results.csv")
|
||||||
|
|
||||||
|
results = []
|
||||||
|
with open(csv_path, "r", encoding="utf-8") as f:
|
||||||
|
reader = csv.DictReader(f)
|
||||||
|
for row in reader:
|
||||||
|
row['time_ms'] = float(row['time_ms'])
|
||||||
|
row['visited'] = int(row['visited'])
|
||||||
|
row['path_len'] = int(row['path_len'])
|
||||||
|
results.append(row)
|
||||||
|
|
||||||
|
mazes = sorted(set(r['maze'] for r in results))
|
||||||
|
algorithms = sorted(set(r['algorithm'] for r in results))
|
||||||
|
|
||||||
|
x_labels = []
|
||||||
|
for m in mazes:
|
||||||
|
for a in algorithms:
|
||||||
|
x_labels.append(f"{m.replace('.txt','')}\n{a}")
|
||||||
|
|
||||||
|
# График 1: Время выполнения
|
||||||
|
plt.figure(figsize=(12, 6))
|
||||||
|
times = []
|
||||||
|
for m in mazes:
|
||||||
|
for a in algorithms:
|
||||||
|
val = [r['time_ms'] for r in results if r['maze'] == m and r['algorithm'] == a]
|
||||||
|
times.append(val[0] if val else 0)
|
||||||
|
plt.bar(x_labels, times)
|
||||||
|
plt.ylabel("Время (мс)")
|
||||||
|
plt.title("Сравнение времени выполнения алгоритмов")
|
||||||
|
plt.xticks(rotation=45, ha='right')
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig(os.path.join(script_dir, "plot_time.png"), dpi=150)
|
||||||
|
plt.close()
|
||||||
|
print("Сохранён: experiments/plot_time.png")
|
||||||
|
|
||||||
|
# График 2: Посещённые клетки
|
||||||
|
plt.figure(figsize=(12, 6))
|
||||||
|
visited_list = []
|
||||||
|
for m in mazes:
|
||||||
|
for a in algorithms:
|
||||||
|
val = [r['visited'] for r in results if r['maze'] == m and r['algorithm'] == a]
|
||||||
|
visited_list.append(val[0] if val else 0)
|
||||||
|
plt.bar(x_labels, visited_list)
|
||||||
|
plt.ylabel("Посещено клеток")
|
||||||
|
plt.title("Сравнение количества посещённых клеток")
|
||||||
|
plt.xticks(rotation=45, ha='right')
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig(os.path.join(script_dir, "plot_visited.png"), dpi=150)
|
||||||
|
plt.close()
|
||||||
|
print("Сохранён: experiments/plot_visited.png")
|
||||||
|
|
||||||
|
# График 3: Длина пути
|
||||||
|
plt.figure(figsize=(12, 6))
|
||||||
|
path_list = []
|
||||||
|
for m in mazes:
|
||||||
|
for a in algorithms:
|
||||||
|
val = [r['path_len'] for r in results if r['maze'] == m and r['algorithm'] == a]
|
||||||
|
path_list.append(val[0] if val else 0)
|
||||||
|
plt.bar(x_labels, path_list)
|
||||||
|
plt.ylabel("Длина пути")
|
||||||
|
plt.title("Сравнение длины найденного пути")
|
||||||
|
plt.xticks(rotation=45, ha='right')
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig(os.path.join(script_dir, "plot_path.png"), dpi=150)
|
||||||
|
plt.close()
|
||||||
|
print("Сохранён: experiments/plot_path.png")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
plot_results()
|
||||||
BIN
BrychkinKA/experiments/plot_path.png
Normal file
|
After Width: | Height: | Size: 35 KiB |