Compare commits
1487 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7eb345fedc | |||
| 571bde5320 | |||
| 182e97e0be | |||
| 7cf11cb3ef | |||
| 3ec4ea8e0f | |||
| 471618449c | |||
| 1549daa6a9 | |||
| 49bbdda926 | |||
| 8774dfb38c | |||
| 634a882918 | |||
| 865cdc7a4e | |||
| 8ebedbaf2a | |||
| f1bb79cd58 | |||
| 80948d5363 | |||
| f34137546b | |||
| e86eb5766a | |||
| e84264cf8b | |||
| cdd44303e4 | |||
| 59ff0051d9 | |||
| 64c96253ba | |||
| 7fc4e1ab10 | |||
| 8f9da375c1 | |||
| 672af89f8c | |||
| ba9eaf7570 | |||
| bb1eebe572 | |||
| 8184739210 | |||
| 9caaeb37a9 | |||
| 9c5302e8ff | |||
| f95819cb5b | |||
| 6ee3ec91a1 | |||
| 6ad28df31e | |||
| 1d6032f5a1 | |||
| 0b2b19a2ca | |||
| 69d425a34e | |||
| 106ea57b17 | |||
| 5a247e8491 | |||
| c1236a618f | |||
| 46fd403eb8 | |||
| 48b8937bf3 | |||
| 82c6dd2718 | |||
| 56e881192a | |||
| ac36eb6c81 | |||
| 1f488408aa | |||
| 78222d064e | |||
| de3a8108fd | |||
| 091488afe9 | |||
| fd367d2c1a | |||
| 76efec5691 | |||
| 39a16eb965 | |||
| 4ebecfe147 | |||
| 889e433173 | |||
| 625fae67e0 | |||
| f3669cc9e0 | |||
| 1853850bfb | |||
| 511653fafc | |||
| 714ea579dd | |||
| 8f77d752d8 | |||
| 3fc82042d0 | |||
| 70dd7d781a | |||
| ccf09cb407 | |||
| 7e83ca2d67 | |||
| 5d337d933b | |||
| e5f1418c1b | |||
| e6b4e51999 | |||
| 040d3236d9 | |||
| 149dc5a8f9 | |||
| 0fe5753b41 | |||
| b5f5372e4f | |||
| 565e8539d0 | |||
| 2fca522d2c | |||
| c8b87192e4 | |||
| ea5bcb4393 | |||
| c259717203 | |||
| d5b340d2d3 | |||
| ad50ed566b | |||
| c1d8b9d5fe | |||
| 1818154b9a | |||
| bd4d2dad4f | |||
| 6b7f970ff5 | |||
| e4a9efb465 | |||
| 660857b168 | |||
| bfc98b1c4b | |||
| b5ddddbcff | |||
| c4b3aa03af | |||
| 4071df31c4 | |||
| d77c2d9b48 | |||
| d18d60135e | |||
| 7459cb5db3 | |||
| fce766d8be | |||
| 9214159de9 | |||
| 4a8e12a2dc | |||
| fa0e805e3a | |||
| 69dbcddfac | |||
| 86d5e69491 | |||
| 7598643568 | |||
| dbfa8330f1 | |||
| 237e81e81b | |||
| 5070ea9778 | |||
| dd1a66d373 | |||
| 284d7d5b81 | |||
| c4eadc0846 | |||
| 94bf00d7ce | |||
| 5046c45433 | |||
| 073c4112bc | |||
| f9befa866d | |||
| 85985fc76b | |||
| 58d48176e8 | |||
| 68e0ea2ac1 | |||
| 3b08582540 | |||
| c07a4807bf | |||
| ac4f3924d8 | |||
| 8cb368fdd4 | |||
| 1da6c3f5a8 | |||
| 5dfc22f2f3 | |||
| 3b3ab992d5 | |||
| ad9b2368d9 | |||
| a877232c86 | |||
| d4ffb90183 | |||
| ce1ac8e31b | |||
| 513522a989 | |||
| 8df013a735 | |||
| 1e5d511ec7 | |||
| 792ebeb415 | |||
| 2cb8d56df5 | |||
| abc82b0399 | |||
| f8aef84df4 | |||
| 64df6259b5 | |||
| eb40ef6602 | |||
| 220e1367de | |||
| 3e16677577 | |||
| f8b45a8d6b | |||
| 27cdfbe729 | |||
| a4608f8b82 | |||
| 3e5f6986d9 | |||
| 306380eb55 | |||
| 7514883955 | |||
| 1746a0df23 | |||
| d2e6975a50 | |||
| 48bf552c39 | |||
| 4a1fdb4388 | |||
| 8b568c2b8c | |||
| c907789822 | |||
| 49d955eda9 | |||
| 8a142e0810 | |||
| 4c37bbcc36 | |||
| 6adf9d0516 | |||
| f25d39d59e | |||
| 6b73e194fd | |||
| c4a7d2a1bf | |||
| 678e6f2c07 | |||
| 22a56b8f53 | |||
| ba1a45fca3 | |||
|
|
8552f7bb4a | ||
|
|
b0fccd4611 | ||
|
|
d37835d9dd | ||
|
|
8f3e385cd0 | ||
|
|
1b5452c82c | ||
|
|
cee96df005 | ||
|
|
7b74e7d74b | ||
| 2898dd52d8 | |||
| c7051d45f9 | |||
| 4f99a56cb1 | |||
| 6ae50d6a45 | |||
| 06a9bd3bc4 | |||
| 2ec77378a6 | |||
| 6333b7a65c | |||
| 1b242a66b6 | |||
| 8ea0f2275e | |||
| d238e70691 | |||
| 6d882c133a | |||
| a550f7ff90 | |||
| 2e9420db8d | |||
| 8070c09dfa | |||
| 60198cae93 | |||
| 74e30978aa | |||
| 9f14ed95db | |||
| 450f278242 | |||
| 53bec3f16c | |||
| d0e03a0628 | |||
| 5fbe055481 | |||
| 4fd3db10ee | |||
| e2c14d8a47 | |||
| 30fa1affb3 | |||
| 8afdfdb74a | |||
| 2735239aca | |||
| 186cd9f688 | |||
| 210ea8ece8 | |||
| a4d9a226c1 | |||
| bcdaf65dbc | |||
| cec177e26d | |||
| 2d36dd7ef3 | |||
| ad704a27ab | |||
| 3b1c21a8b1 | |||
| 7f540020bc | |||
| 52fbd9c2b1 | |||
| 8deead6d69 | |||
| c5eef27dc9 | |||
| 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 | |||
| 2ff3ae7e2a | |||
| 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 | |||
| efc208b7eb | |||
| 3807ea7c02 | |||
| faab3a3276 | |||
| 892715f2e9 | |||
| 0f99daf4bd | |||
| 8d20ade34e | |||
| a32ff24a11 | |||
| afd5626bf0 | |||
| 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 | |||
| 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 | |||
| f04f36c12e | |||
| 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 | |||
| 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 | |||
| 188077da56 | |||
| 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 | |||
| 032b22c79f | |||
| 2163ccd0aa | |||
| 4e29dfb4bb | |||
| f89f54eddc | |||
| bd678b4716 | |||
| 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 | |||
| 098e0f3a32 | |||
| f4e8b9732e | |||
|
|
5525af0b26 | ||
|
|
98c17e18fa | ||
|
|
05a9bdd8f4 | ||
| c9448652cf | |||
|
|
d05d292101 | ||
|
|
e45f8cba64 | ||
| 8dafdab90e | |||
|
|
3fa79f06c3 | ||
| 8a6344f301 | |||
| 4422dcb9e7 | |||
|
|
e10b075b06 | ||
| 8331065da2 | |||
| f89d10b615 | |||
| 47404cfa9d | |||
| 74935d19f7 | |||
| 91f5558c26 | |||
| 2670cbf971 | |||
| ee0aecd7ca | |||
| a27f861edf | |||
| 838c2afa68 | |||
| dd054c4034 | |||
| 2449eeb26f | |||
| 3869ff11ed | |||
| 45edc73aa2 | |||
| d0b791287f | |||
| df29d66f15 | |||
| 06b5b017e4 | |||
| 64978d3245 | |||
| 01eabf2655 | |||
| 9de2a5fb39 | |||
|
|
27865cf7d4 | ||
|
|
4f28fd2015 | ||
|
|
fd110e4193 | ||
|
|
82f0fc13e9 | ||
| 6932687d9c | |||
|
|
014ed32f08 | ||
| 66c2a4cfda | |||
| 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 | |||
| 56509603c7 | |||
|
|
c7c181f30b | ||
| 82e988c965 | |||
| 58daf860ed | |||
| a095930b97 | |||
| e1ad783b49 | |||
| 76b52b99e4 | |||
| 09e7e59f55 | |||
| f914a177a5 | |||
| c32fd836b5 | |||
| c76eb6f91b | |||
| 0046bde759 | |||
| be927fd028 | |||
| 409b7dde52 | |||
| eeb0e16665 | |||
| 1d224e6171 | |||
| 274784ea0a | |||
| d43389ec5e | |||
| ff0f375cb3 | |||
| ee11ed639f | |||
| b07adc7cb4 | |||
| 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 | |||
| d467c49886 | |||
| 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 | ||
| ab854a04dd | |||
| c121e51b1f | |||
| 3e175eb367 | |||
| 3e5ee4a5a4 | |||
| aa6fbb0692 | |||
|
|
9eedcecf0c | ||
| 2204ca3dc8 | |||
| 764e6a3a2c | |||
| ec48b13150 | |||
| 3cfd61e6cc | |||
|
|
57c811ece4 | ||
| 0d86929b62 | |||
| e442988725 | |||
| af2f607a3b | |||
| 96532a99fb | |||
| 809f768703 | |||
| 154b9b8b65 | |||
| aeb608a5d3 | |||
| f541180fcf | |||
| 134b330145 | |||
|
|
7c9a1f8b51 | ||
|
|
1fd05fbf25 | ||
|
|
ae9b2a46b6 | ||
| 8dc1d57252 | |||
|
|
d7a13c9fe6 | ||
| 8124ef4551 | |||
| 8744999b03 | |||
| 01f0e5dd88 | |||
|
|
0c990ece48 | ||
|
|
9af52f3f7b | ||
|
|
9e70450c50 | ||
|
|
a7323e3762 | ||
| 365f830e05 | |||
| 7fe5c11997 | |||
| 969b9ee469 | |||
| 0c6a8bd4db | |||
|
|
b849e3a7bc | ||
| 2e0eec1543 | |||
|
|
17fc44d83e | ||
| 4b24d66ab7 | |||
|
|
48d37c6f77 | ||
| 4a26e7aa89 | |||
| e0eb3ae13a | |||
| f983a6fa1d | |||
|
|
356e4b53dd | ||
| b7cea24df4 | |||
| 8221562750 | |||
| 789d8ef3bb | |||
|
|
6e433a36ae | ||
| a50fd0bfc1 | |||
|
|
de2b98bc01 | ||
| c53b093c38 | |||
| dd3b126d62 | |||
| 048b211945 | |||
| 6d05295507 | |||
| 55d8ed29c6 | |||
| af2d62a446 | |||
| fe5a8bea89 | |||
|
|
8fb5738b50 | ||
| b4b3140603 | |||
|
|
90503c7b92 | ||
|
|
bcde039ddd | ||
| ddc6d9b877 | |||
| e1e08990e0 | |||
|
|
fe50036baf | ||
|
|
8f2e46907f | ||
|
|
afa3504609 | ||
|
|
ccb7c7a5da | ||
|
|
6ef4820d9e | ||
| c3131ce0f6 | |||
| 8a523c9afb | |||
| e8ea43f9b3 | |||
|
|
57c8ef048f | ||
| c774f9182a | |||
| d346e6c40b | |||
| c865eb2b88 | |||
| 194d822b62 | |||
|
|
589787851b | ||
| b34b2b76c8 | |||
| 46f73eaa47 | |||
| 5eab591f1a | |||
| 768a06fbb6 | |||
| 141bb2bbb5 | |||
| f7b4f75488 | |||
| 802b72d10b | |||
| 0cd1012949 | |||
| 6b2f019b11 | |||
| 5964cc81ab | |||
| 9f395a7b9a | |||
| 362b487266 | |||
| 8a4eb2ab7b | |||
| cc5764f86a | |||
| c276d52949 | |||
| a40c5f579a | |||
| 2ea1b50bc7 | |||
| d5450e0063 | |||
| b35fa6725b | |||
| de62717491 | |||
| 902ad5ef53 | |||
|
|
343a06519a | ||
| a2621ad508 | |||
| 95f3cd28df | |||
| 57adafbebd | |||
|
|
b881e05410 | ||
| d33fd62603 | |||
| e5733fd15d | |||
| 8dff4985b9 | |||
| 0f92398229 | |||
| 8c3a030957 | |||
| 6c0751dc68 | |||
| 5f779a102c | |||
| ad692ac5b8 | |||
| b4daf9ea72 | |||
| 71cb098a69 | |||
| c46e1aa8e2 | |||
| 595a3518d9 | |||
|
|
b98c87e4d5 | ||
|
|
41ad3771e8 | ||
|
|
de77a1109d | ||
| a0653117cd | |||
| 41ce5b60cf | |||
| 4428a8e88d | |||
| 195de5c70f | |||
| 60ecbdb35d | |||
|
|
3eab4cc3aa | ||
| 73cad3b59f | |||
| 1de0cee11d | |||
| 306299755c | |||
| 5dcd06963c | |||
| 65fea33bd4 | |||
|
|
ee26f74767 | ||
| 7000ccc96c | |||
|
|
7db86ec769 | ||
|
|
bef613db62 | ||
| a3c03ab3b4 | |||
| aadad1272e | |||
| 0c578f46e5 | |||
| b168cd4f7b | |||
| 391d8a79d1 | |||
|
|
ee723b700c | ||
| 87996f3d85 | |||
| 2a6f10cbf9 | |||
|
|
f2d51f5bc5 | ||
|
|
cb7dfdcf0d | ||
|
|
288f562fa0 | ||
|
|
ed603fc6dd | ||
| 27e1e98bf4 | |||
| 487ff6d741 | |||
|
|
9cf5ffa339 | ||
| 9ad53dbe15 | |||
| fd82609af4 | |||
|
|
0ff7405de9 | ||
| 2e2a48fd30 | |||
|
|
34872ab84e | ||
| 3694f25de8 | |||
| b16d1f6731 | |||
| 9f68a759d8 | |||
| ca2a7eb3b6 | |||
| 7fec6872a1 | |||
|
|
568d2cbf23 | ||
|
|
fb021ba018 | ||
|
|
1d508ceb9b | ||
| 914063a196 | |||
|
|
f6a224a94a | ||
|
|
41acf4dab2 | ||
| b5469155fa | |||
| f15908a49d | |||
| 7c9c594771 | |||
| aca0eb0c84 |
5
.idea/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
# Игнорируемые файлы по умолчанию
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# HTTP-клиент на основе редактора
|
||||||
|
/httpRequests/
|
||||||
8
.idea/2026-rff_mp.iml
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="PYTHON_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$" />
|
||||||
|
<orderEntry type="jdk" jdkName="Python 3.14" jdkType="Python SDK" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
6
.idea/inspectionProfiles/profiles_settings.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<settings>
|
||||||
|
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||||
|
<version value="1.0" />
|
||||||
|
</settings>
|
||||||
|
</component>
|
||||||
7
.idea/misc.xml
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Black">
|
||||||
|
<option name="sdkName" value="Python 3.14" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.14" project-jdk-type="Python SDK" />
|
||||||
|
</project>
|
||||||
8
.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/2026-rff_mp.iml" filepath="$PROJECT_DIR$/.idea/2026-rff_mp.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
.idea/vcs.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
1
2026-rff_mp
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 52c001a380431727397e4275c2be9d94fe5fcc8d
|
||||||
1
AgapovaDS/428b.md
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
428b
|
||||||
31
AgapovaDS/docs/data/1-st/experiment_results.csv
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
Structure,Mode,Repetition,Insert (sec),Find (sec),Delete (sec)
|
||||||
|
LinkedList,random,1,0.022518936000324175,0.0022566010002265102,0.00023215900000650436
|
||||||
|
LinkedList,random,2,0.020449046000067028,0.00233427400007713,0.0002826350000759703
|
||||||
|
LinkedList,random,3,0.020126345999869955,0.002262161000089691,0.0002053490002253966
|
||||||
|
LinkedList,random,4,0.018700520000038523,0.0021239550001155294,0.00021164700001463643
|
||||||
|
LinkedList,random,5,0.019754801000090083,0.0024997460000122373,0.00023158399972089683
|
||||||
|
HashTable,random,1,0.0029892609995840758,0.0003534970001055626,2.990000029967632e-05
|
||||||
|
HashTable,random,2,0.00424448100011432,0.00041066700032388326,4.1896999846358085e-05
|
||||||
|
HashTable,random,3,0.004175077000127203,0.0004925200000798213,2.9787000130454544e-05
|
||||||
|
HashTable,random,4,0.004567926000163425,0.00033349500017720857,3.164999998261919e-05
|
||||||
|
HashTable,random,5,0.0026621540000633104,0.00031125700024858816,2.81510001514107e-05
|
||||||
|
BST,random,1,0.0011096680000264314,9.346200022264384e-05,3.9635000121052144e-05
|
||||||
|
BST,random,2,0.0010248069997942366,9.059800004251883e-05,2.1142000150575768e-05
|
||||||
|
BST,random,3,0.0010313749999113497,9.237799986294704e-05,1.8248000287712784e-05
|
||||||
|
BST,random,4,0.0010483440000825794,9.526299982098863e-05,0.0001046150000547641
|
||||||
|
BST,random,5,0.0010863010002140072,8.332400011568097e-05,1.7150000076071592e-05
|
||||||
|
LinkedList,sorted,1,0.022635587000422674,0.0023248429997693165,0.00031860600029176567
|
||||||
|
LinkedList,sorted,2,0.02307340400011526,0.002558939999744325,0.00023637899994355394
|
||||||
|
LinkedList,sorted,3,0.01936496400003307,0.002565375999893149,0.00019060200020248885
|
||||||
|
LinkedList,sorted,4,0.018800279000060982,0.0021075829999972484,0.00018179399967266363
|
||||||
|
LinkedList,sorted,5,0.019234453000080975,0.002213534000020445,0.0002037049998762086
|
||||||
|
HashTable,sorted,1,0.0031771270000717777,0.0006652990000475256,6.005399973219028e-05
|
||||||
|
HashTable,sorted,2,0.004056166999816924,0.00058001300021715,3.078500003539375e-05
|
||||||
|
HashTable,sorted,3,0.00331192200019359,0.00027228699991610483,2.684700029931264e-05
|
||||||
|
HashTable,sorted,4,0.002749069000401505,0.0002677190000213159,2.1935999939159956e-05
|
||||||
|
HashTable,sorted,5,0.002638604000367195,0.0003338189999340102,1.83020001713885e-05
|
||||||
|
BST,sorted,1,0.037994623000031424,0.0035877690002052987,0.0011276019999968412
|
||||||
|
BST,sorted,2,0.038043681000090146,0.0031297909999921103,0.00094645799981663
|
||||||
|
BST,sorted,3,0.03629641699990316,0.0027566620001380215,0.0012528499996733444
|
||||||
|
BST,sorted,4,0.03509285000018281,0.003267435999987356,0.0011822649998975976
|
||||||
|
BST,sorted,5,0.038755655999921146,0.0064144259999920905,0.0018493429997761268
|
||||||
|
BIN
AgapovaDS/docs/data/1-st/performance_comparison.png
Normal file
|
After Width: | Height: | Size: 79 KiB |
335
AgapovaDS/docs/data/1-st/phonebook.py
Normal file
|
|
@ -0,0 +1,335 @@
|
||||||
|
import random
|
||||||
|
import time
|
||||||
|
import sys
|
||||||
|
import csv
|
||||||
|
import os
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
sys.setrecursionlimit(10000)
|
||||||
|
|
||||||
|
ll_head = None
|
||||||
|
|
||||||
|
def ll_insert(name, phone):
|
||||||
|
global ll_head
|
||||||
|
cur = ll_head
|
||||||
|
while cur is not None:
|
||||||
|
if cur['name'] == name:
|
||||||
|
cur['phone'] = phone
|
||||||
|
return
|
||||||
|
cur = cur['next']
|
||||||
|
new_node = {'name': name, 'phone': phone, 'next': ll_head}
|
||||||
|
ll_head = new_node
|
||||||
|
|
||||||
|
def ll_find(name):
|
||||||
|
cur = ll_head
|
||||||
|
while cur is not None:
|
||||||
|
if cur['name'] == name:
|
||||||
|
return cur['phone']
|
||||||
|
cur = cur['next']
|
||||||
|
return None
|
||||||
|
|
||||||
|
def ll_delete(name):
|
||||||
|
global ll_head
|
||||||
|
if ll_head is None:
|
||||||
|
return
|
||||||
|
if ll_head['name'] == name:
|
||||||
|
ll_head = ll_head['next']
|
||||||
|
return
|
||||||
|
prev = ll_head
|
||||||
|
cur = ll_head['next']
|
||||||
|
while cur is not None:
|
||||||
|
if cur['name'] == name:
|
||||||
|
prev['next'] = cur['next']
|
||||||
|
return
|
||||||
|
prev = cur
|
||||||
|
cur = cur['next']
|
||||||
|
|
||||||
|
def ll_list_all():
|
||||||
|
records = []
|
||||||
|
cur = ll_head
|
||||||
|
while cur is not None:
|
||||||
|
records.append((cur['name'], cur['phone']))
|
||||||
|
cur = cur['next']
|
||||||
|
records.sort(key=lambda x: x[0])
|
||||||
|
return records
|
||||||
|
|
||||||
|
BUCKET_COUNT = 10
|
||||||
|
buckets = [None] * BUCKET_COUNT
|
||||||
|
|
||||||
|
def hash_func(name):
|
||||||
|
s = 0
|
||||||
|
for ch in name:
|
||||||
|
s += ord(ch)
|
||||||
|
return s % BUCKET_COUNT
|
||||||
|
|
||||||
|
def ht_insert(name, phone):
|
||||||
|
global buckets
|
||||||
|
idx = hash_func(name)
|
||||||
|
cur = buckets[idx]
|
||||||
|
while cur is not None:
|
||||||
|
if cur['name'] == name:
|
||||||
|
cur['phone'] = phone
|
||||||
|
return
|
||||||
|
cur = cur['next']
|
||||||
|
new_node = {'name': name, 'phone': phone, 'next': buckets[idx]}
|
||||||
|
buckets[idx] = new_node
|
||||||
|
|
||||||
|
def ht_find(name):
|
||||||
|
idx = hash_func(name)
|
||||||
|
cur = buckets[idx]
|
||||||
|
while cur is not None:
|
||||||
|
if cur['name'] == name:
|
||||||
|
return cur['phone']
|
||||||
|
cur = cur['next']
|
||||||
|
return None
|
||||||
|
|
||||||
|
def ht_delete(name):
|
||||||
|
global buckets
|
||||||
|
idx = hash_func(name)
|
||||||
|
head = buckets[idx]
|
||||||
|
if head is None:
|
||||||
|
return
|
||||||
|
if head['name'] == name:
|
||||||
|
buckets[idx] = head['next']
|
||||||
|
return
|
||||||
|
prev = head
|
||||||
|
cur = head['next']
|
||||||
|
while cur is not None:
|
||||||
|
if cur['name'] == name:
|
||||||
|
prev['next'] = cur['next']
|
||||||
|
return
|
||||||
|
prev = cur
|
||||||
|
cur = cur['next']
|
||||||
|
|
||||||
|
def ht_list_all():
|
||||||
|
all_records = []
|
||||||
|
for head in buckets:
|
||||||
|
cur = head
|
||||||
|
while cur is not None:
|
||||||
|
all_records.append((cur['name'], cur['phone']))
|
||||||
|
cur = cur['next']
|
||||||
|
all_records.sort(key=lambda x: x[0])
|
||||||
|
return all_records
|
||||||
|
|
||||||
|
bst_root = None
|
||||||
|
|
||||||
|
def bst_create_node(name, phone):
|
||||||
|
return {'name': name, 'phone': phone, 'left': None, 'right': None}
|
||||||
|
|
||||||
|
def bst_insert(name, phone):
|
||||||
|
global bst_root
|
||||||
|
if bst_root is None:
|
||||||
|
bst_root = bst_create_node(name, phone)
|
||||||
|
return
|
||||||
|
current = bst_root
|
||||||
|
while True:
|
||||||
|
if name == current['name']:
|
||||||
|
current['phone'] = phone
|
||||||
|
return
|
||||||
|
elif name < current['name']:
|
||||||
|
if current['left'] is None:
|
||||||
|
current['left'] = bst_create_node(name, phone)
|
||||||
|
return
|
||||||
|
current = current['left']
|
||||||
|
else:
|
||||||
|
if current['right'] is None:
|
||||||
|
current['right'] = bst_create_node(name, phone)
|
||||||
|
return
|
||||||
|
current = current['right']
|
||||||
|
|
||||||
|
def bst_find(name):
|
||||||
|
current = bst_root
|
||||||
|
while current is not None:
|
||||||
|
if name == current['name']:
|
||||||
|
return current['phone']
|
||||||
|
elif name < current['name']:
|
||||||
|
current = current['left']
|
||||||
|
else:
|
||||||
|
current = current['right']
|
||||||
|
return None
|
||||||
|
|
||||||
|
def find_min(node):
|
||||||
|
while node['left'] is not None:
|
||||||
|
node = node['left']
|
||||||
|
return node
|
||||||
|
|
||||||
|
def bst_delete(name):
|
||||||
|
global bst_root
|
||||||
|
def delete_rec(node):
|
||||||
|
if node is None:
|
||||||
|
return None
|
||||||
|
if name < node['name']:
|
||||||
|
node['left'] = delete_rec(node['left'])
|
||||||
|
elif name > node['name']:
|
||||||
|
node['right'] = delete_rec(node['right'])
|
||||||
|
else:
|
||||||
|
if node['left'] is None:
|
||||||
|
return node['right']
|
||||||
|
if node['right'] is None:
|
||||||
|
return node['left']
|
||||||
|
min_node = find_min(node['right'])
|
||||||
|
node['name'] = min_node['name']
|
||||||
|
node['phone'] = min_node['phone']
|
||||||
|
node['right'] = delete_rec(node['right'])
|
||||||
|
return node
|
||||||
|
bst_root = delete_rec(bst_root)
|
||||||
|
|
||||||
|
def bst_list_all():
|
||||||
|
result = []
|
||||||
|
def inorder(node):
|
||||||
|
if node is None:
|
||||||
|
return
|
||||||
|
inorder(node['left'])
|
||||||
|
result.append((node['name'], node['phone']))
|
||||||
|
inorder(node['right'])
|
||||||
|
inorder(bst_root)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def generate_records(n):
|
||||||
|
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 run_experiment():
|
||||||
|
N = 1000
|
||||||
|
base = generate_records(N)
|
||||||
|
shuffled = base.copy()
|
||||||
|
random.shuffle(shuffled)
|
||||||
|
sorted_records = sorted(base, key=lambda x: x[0])
|
||||||
|
|
||||||
|
structures = [
|
||||||
|
('LinkedList', ll_insert, ll_find, ll_delete, ll_list_all),
|
||||||
|
('HashTable', ht_insert, ht_find, ht_delete, ht_list_all),
|
||||||
|
('BST', bst_insert, bst_find, bst_delete, bst_list_all)
|
||||||
|
]
|
||||||
|
|
||||||
|
all_results = [] # для CSV: список словарей
|
||||||
|
repeats = 5
|
||||||
|
|
||||||
|
for mode_name, data in [('random', shuffled), ('sorted', sorted_records)]:
|
||||||
|
for struct_name, ins, fnd, dele, lst in structures:
|
||||||
|
print(f"Testing {struct_name} on {mode_name}...")
|
||||||
|
for rep in range(repeats):
|
||||||
|
# сброс структур
|
||||||
|
global ll_head, buckets, bst_root
|
||||||
|
ll_head = None
|
||||||
|
buckets = [None] * BUCKET_COUNT
|
||||||
|
bst_root = None
|
||||||
|
|
||||||
|
# вставка
|
||||||
|
t0 = time.perf_counter()
|
||||||
|
for name, phone in data:
|
||||||
|
ins(name, phone)
|
||||||
|
t1 = time.perf_counter()
|
||||||
|
insert_time = t1 - t0
|
||||||
|
|
||||||
|
# поиск 110 записей (100 существующих + 10 несуществующих)
|
||||||
|
existing = [name for name, _ in data]
|
||||||
|
sample = random.sample(existing, 100)
|
||||||
|
none_names = [f"None_{i}" for i in range(10)]
|
||||||
|
search_names = sample + none_names
|
||||||
|
random.shuffle(search_names)
|
||||||
|
t0 = time.perf_counter()
|
||||||
|
for name in search_names:
|
||||||
|
fnd(name)
|
||||||
|
t1 = time.perf_counter()
|
||||||
|
find_time = t1 - t0
|
||||||
|
|
||||||
|
# удаление 10 записей
|
||||||
|
to_delete = random.sample(existing, 10)
|
||||||
|
t0 = time.perf_counter()
|
||||||
|
for name in to_delete:
|
||||||
|
dele(name)
|
||||||
|
t1 = time.perf_counter()
|
||||||
|
delete_time = t1 - t0
|
||||||
|
|
||||||
|
all_results.append({
|
||||||
|
'Structure': struct_name,
|
||||||
|
'Mode': mode_name,
|
||||||
|
'Repetition': rep+1,
|
||||||
|
'Insert (sec)': insert_time,
|
||||||
|
'Find (sec)': find_time,
|
||||||
|
'Delete (sec)': delete_time
|
||||||
|
})
|
||||||
|
|
||||||
|
# Сохранение CSV
|
||||||
|
output_dir = "docs/data/1-st"
|
||||||
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
|
csv_path = os.path.join(output_dir, "experiment_results.csv")
|
||||||
|
with open(csv_path, 'w', newline='', encoding='utf-8') as f:
|
||||||
|
fieldnames = ['Structure', 'Mode', 'Repetition', 'Insert (sec)', 'Find (sec)', 'Delete (sec)']
|
||||||
|
writer = csv.DictWriter(f, fieldnames=fieldnames)
|
||||||
|
writer.writeheader()
|
||||||
|
writer.writerows(all_results)
|
||||||
|
print(f"\nРезультаты сохранены в {csv_path}")
|
||||||
|
|
||||||
|
avg_data = {}
|
||||||
|
for r in all_results:
|
||||||
|
key = (r['Structure'], r['Mode'])
|
||||||
|
if key not in avg_data:
|
||||||
|
avg_data[key] = {'Insert': [], 'Find': [], 'Delete': []}
|
||||||
|
avg_data[key]['Insert'].append(r['Insert (sec)'])
|
||||||
|
avg_data[key]['Find'].append(r['Find (sec)'])
|
||||||
|
avg_data[key]['Delete'].append(r['Delete (sec)'])
|
||||||
|
|
||||||
|
structures_list = ['LinkedList', 'HashTable', 'BST']
|
||||||
|
modes_list = ['random', 'sorted']
|
||||||
|
insert_vals = {mode: [] for mode in modes_list}
|
||||||
|
find_vals = {mode: [] for mode in modes_list}
|
||||||
|
delete_vals = {mode: [] for mode in modes_list}
|
||||||
|
|
||||||
|
for mode in modes_list:
|
||||||
|
for struct in structures_list:
|
||||||
|
key = (struct, mode)
|
||||||
|
if key in avg_data:
|
||||||
|
insert_avg = sum(avg_data[key]['Insert']) / len(avg_data[key]['Insert'])
|
||||||
|
find_avg = sum(avg_data[key]['Find']) / len(avg_data[key]['Find'])
|
||||||
|
delete_avg = sum(avg_data[key]['Delete']) / len(avg_data[key]['Delete'])
|
||||||
|
else:
|
||||||
|
insert_avg = find_avg = delete_avg = 0
|
||||||
|
insert_vals[mode].append(insert_avg)
|
||||||
|
find_vals[mode].append(find_avg)
|
||||||
|
delete_vals[mode].append(delete_avg)
|
||||||
|
|
||||||
|
# Рисуем три столбчатые диаграммы
|
||||||
|
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
|
||||||
|
x = np.arange(len(structures_list))
|
||||||
|
width = 0.35
|
||||||
|
|
||||||
|
for ax, op_data, op_label, ylabel in zip(
|
||||||
|
axes,
|
||||||
|
[insert_vals, find_vals, delete_vals],
|
||||||
|
['Insert', 'Find', 'Delete'],
|
||||||
|
['Время вставки (с)', 'Время поиска (с)', 'Время удаления (с)']
|
||||||
|
):
|
||||||
|
random_vals = op_data['random']
|
||||||
|
sorted_vals = op_data['sorted']
|
||||||
|
ax.bar(x - width/2, random_vals, width, label='Случайный порядок', color='skyblue')
|
||||||
|
ax.bar(x + width/2, sorted_vals, width, label='Отсортированный порядок', color='salmon')
|
||||||
|
ax.set_xticks(x)
|
||||||
|
ax.set_xticklabels(structures_list)
|
||||||
|
ax.set_ylabel(ylabel)
|
||||||
|
ax.set_title(op_label)
|
||||||
|
ax.legend()
|
||||||
|
|
||||||
|
plt.tight_layout()
|
||||||
|
png_path = os.path.join(output_dir, "performance_comparison.png")
|
||||||
|
plt.savefig(png_path, dpi=150)
|
||||||
|
print(f"График сохранён в {png_path}")
|
||||||
|
plt.show()
|
||||||
|
|
||||||
|
# Вывод средних значений в консоль (для отчёта)
|
||||||
|
print("\nСредние значения (сек):")
|
||||||
|
print("Структура\tРежим\tВставка\tПоиск\tУдаление")
|
||||||
|
for (struct, mode), vals in avg_data.items():
|
||||||
|
ins_avg = sum(vals['Insert'])/len(vals['Insert'])
|
||||||
|
find_avg = sum(vals['Find'])/len(vals['Find'])
|
||||||
|
del_avg = sum(vals['Delete'])/len(vals['Delete'])
|
||||||
|
print(f"{struct}\t{mode}\t{ins_avg:.6f}\t{find_avg:.6f}\t{del_avg:.6f}")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
run_experiment()
|
||||||
430
AgapovaDS/docs/data/2-nd/maze.py
Normal file
|
|
@ -0,0 +1,430 @@
|
||||||
|
import time
|
||||||
|
import random
|
||||||
|
import csv
|
||||||
|
import os
|
||||||
|
from collections import deque
|
||||||
|
import heapq
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
class Cell:
|
||||||
|
def __init__(self, x, y, is_wall=False, is_start=False, is_exit=False):
|
||||||
|
self.x = x
|
||||||
|
self.y = y
|
||||||
|
self.is_wall = is_wall
|
||||||
|
self.is_start = is_start
|
||||||
|
self.is_exit = is_exit
|
||||||
|
|
||||||
|
def is_passable(self):
|
||||||
|
return not self.is_wall
|
||||||
|
|
||||||
|
|
||||||
|
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_cell = None
|
||||||
|
self.exit_cell = None
|
||||||
|
|
||||||
|
def get_cell(self, x, y):
|
||||||
|
if 0 <= x < self.width and 0 <= y < self.height:
|
||||||
|
return self.grid[x][y]
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_neighbors(self, cell):
|
||||||
|
neighbors = []
|
||||||
|
for dx, dy in [(-1,0), (1,0), (0,-1), (0,1)]:
|
||||||
|
nx, ny = cell.x + dx, cell.y + dy
|
||||||
|
neighbor = self.get_cell(nx, ny)
|
||||||
|
if neighbor and neighbor.is_passable():
|
||||||
|
neighbors.append(neighbor)
|
||||||
|
return neighbors
|
||||||
|
|
||||||
|
def save_to_file(self, filename):
|
||||||
|
"""Сохраняет лабиринт в текстовый файл."""
|
||||||
|
with open(filename, 'w', encoding='utf-8') as f:
|
||||||
|
for y in range(self.height):
|
||||||
|
row = ''
|
||||||
|
for x in range(self.width):
|
||||||
|
cell = self.get_cell(x, y)
|
||||||
|
if cell.is_wall:
|
||||||
|
row += '#'
|
||||||
|
elif cell.is_start:
|
||||||
|
row += 'S'
|
||||||
|
elif cell.is_exit:
|
||||||
|
row += 'E'
|
||||||
|
else:
|
||||||
|
row += ' '
|
||||||
|
f.write(row + '\n')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class MazeBuilder:
|
||||||
|
def build_from_file(self, filename):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
class TextFileMazeBuilder(MazeBuilder):
|
||||||
|
def build_from_file(self, filename):
|
||||||
|
with open(filename, 'r', encoding='utf-8') as f:
|
||||||
|
lines = f.readlines()
|
||||||
|
lines = [line.rstrip('\n') for line in lines if line.strip() != '']
|
||||||
|
if not lines:
|
||||||
|
raise ValueError("Файл пуст")
|
||||||
|
height = len(lines)
|
||||||
|
width = max(len(line) for line in lines)
|
||||||
|
maze = Maze(width, height)
|
||||||
|
for y, line in enumerate(lines):
|
||||||
|
for x, ch in enumerate(line):
|
||||||
|
if x >= width:
|
||||||
|
break
|
||||||
|
cell = maze.get_cell(x, y)
|
||||||
|
if ch == '#':
|
||||||
|
cell.is_wall = True
|
||||||
|
elif ch == 'S':
|
||||||
|
cell.is_start = True
|
||||||
|
maze.start_cell = cell
|
||||||
|
elif ch == 'E':
|
||||||
|
cell.is_exit = True
|
||||||
|
maze.exit_cell = cell
|
||||||
|
if maze.start_cell is None or maze.exit_cell is None:
|
||||||
|
raise ValueError("В лабиринте должны быть S и E")
|
||||||
|
return maze
|
||||||
|
|
||||||
|
|
||||||
|
class PathFindingStrategy:
|
||||||
|
def find_path(self, maze, start, exit):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
class BFSStrategy(PathFindingStrategy):
|
||||||
|
def find_path(self, maze, start, exit):
|
||||||
|
if start == exit:
|
||||||
|
return [start]
|
||||||
|
queue = deque([start])
|
||||||
|
visited = {start}
|
||||||
|
parent = {start: None}
|
||||||
|
while queue:
|
||||||
|
current = queue.popleft()
|
||||||
|
if current == exit:
|
||||||
|
break
|
||||||
|
for neighbor in maze.get_neighbors(current):
|
||||||
|
if neighbor not in visited:
|
||||||
|
visited.add(neighbor)
|
||||||
|
parent[neighbor] = current
|
||||||
|
queue.append(neighbor)
|
||||||
|
if exit not in parent:
|
||||||
|
return []
|
||||||
|
path = []
|
||||||
|
step = exit
|
||||||
|
while step is not None:
|
||||||
|
path.append(step)
|
||||||
|
step = parent[step]
|
||||||
|
path.reverse()
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
class DFSStrategy(PathFindingStrategy):
|
||||||
|
def find_path(self, maze, start, exit):
|
||||||
|
if start == exit:
|
||||||
|
return [start]
|
||||||
|
stack = [start]
|
||||||
|
visited = {start}
|
||||||
|
parent = {start: None}
|
||||||
|
while stack:
|
||||||
|
current = stack.pop()
|
||||||
|
if current == exit:
|
||||||
|
break
|
||||||
|
for neighbor in maze.get_neighbors(current):
|
||||||
|
if neighbor not in visited:
|
||||||
|
visited.add(neighbor)
|
||||||
|
parent[neighbor] = current
|
||||||
|
stack.append(neighbor)
|
||||||
|
if exit not in parent:
|
||||||
|
return []
|
||||||
|
path = []
|
||||||
|
step = exit
|
||||||
|
while step is not None:
|
||||||
|
path.append(step)
|
||||||
|
step = parent[step]
|
||||||
|
path.reverse()
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
class AStarStrategy(PathFindingStrategy):
|
||||||
|
def heuristic(self, a, b):
|
||||||
|
return abs(a.x - b.x) + abs(a.y - b.y)
|
||||||
|
|
||||||
|
def find_path(self, maze, start, exit):
|
||||||
|
if start == exit:
|
||||||
|
return [start]
|
||||||
|
open_set = []
|
||||||
|
heapq.heappush(open_set, (0, id(start), start))
|
||||||
|
came_from = {}
|
||||||
|
g_score = {start: 0}
|
||||||
|
f_score = {start: self.heuristic(start, exit)}
|
||||||
|
while open_set:
|
||||||
|
_, _, current = heapq.heappop(open_set)
|
||||||
|
if current == exit:
|
||||||
|
path = []
|
||||||
|
step = current
|
||||||
|
while step is not None:
|
||||||
|
path.append(step)
|
||||||
|
step = came_from.get(step)
|
||||||
|
path.reverse()
|
||||||
|
return path
|
||||||
|
for neighbor in maze.get_neighbors(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[neighbor] = tentative_g + self.heuristic(neighbor, exit)
|
||||||
|
heapq.heappush(open_set, (f_score[neighbor], id(neighbor), neighbor))
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
class Observer:
|
||||||
|
def update(self, event):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
class ConsoleView(Observer):
|
||||||
|
def __init__(self, maze):
|
||||||
|
self.maze = maze
|
||||||
|
|
||||||
|
def update(self, event):
|
||||||
|
if event == "Поиск начат":
|
||||||
|
print("=== Поиск начат ===")
|
||||||
|
elif event == "Поиск завершён":
|
||||||
|
print("=== Поиск завершён ===")
|
||||||
|
|
||||||
|
def render(self, path=None):
|
||||||
|
path_set = set(path) if path else set()
|
||||||
|
for y in range(self.maze.height):
|
||||||
|
row = ''
|
||||||
|
for x in range(self.maze.width):
|
||||||
|
cell = self.maze.get_cell(x, y)
|
||||||
|
if cell.is_wall:
|
||||||
|
row += '#'
|
||||||
|
elif cell.is_start:
|
||||||
|
row += 'S'
|
||||||
|
elif cell.is_exit:
|
||||||
|
row += 'E'
|
||||||
|
elif cell in path_set:
|
||||||
|
row += '*'
|
||||||
|
else:
|
||||||
|
row += ' '
|
||||||
|
print(row)
|
||||||
|
print()
|
||||||
|
|
||||||
|
|
||||||
|
class MazeSolver:
|
||||||
|
def __init__(self, maze, strategy=None):
|
||||||
|
self.maze = maze
|
||||||
|
self.strategy = strategy
|
||||||
|
self.observers = []
|
||||||
|
|
||||||
|
def set_strategy(self, strategy):
|
||||||
|
self.strategy = strategy
|
||||||
|
|
||||||
|
def attach(self, observer):
|
||||||
|
self.observers.append(observer)
|
||||||
|
|
||||||
|
def detach(self, observer):
|
||||||
|
self.observers.remove(observer)
|
||||||
|
|
||||||
|
def notify(self, event):
|
||||||
|
for obs in self.observers:
|
||||||
|
obs.update(event)
|
||||||
|
|
||||||
|
def solve(self):
|
||||||
|
if self.strategy is None:
|
||||||
|
raise ValueError("Стратегия не установлена")
|
||||||
|
start = self.maze.start_cell
|
||||||
|
exit_cell = self.maze.exit_cell
|
||||||
|
if start is None or exit_cell is None:
|
||||||
|
raise ValueError("Лабиринт не содержит старта или выхода")
|
||||||
|
self.notify("Поиск начат")
|
||||||
|
start_time = time.perf_counter()
|
||||||
|
path = self.strategy.find_path(self.maze, start, exit_cell)
|
||||||
|
end_time = time.perf_counter()
|
||||||
|
elapsed_ms = (end_time - start_time) * 1000
|
||||||
|
self.notify("Поиск завершён")
|
||||||
|
return path, elapsed_ms
|
||||||
|
|
||||||
|
|
||||||
|
def generate_empty_maze(width, height):
|
||||||
|
maze = Maze(width, height)
|
||||||
|
start = maze.get_cell(0, 0)
|
||||||
|
exit_cell = maze.get_cell(width-1, height-1)
|
||||||
|
start.is_start = True
|
||||||
|
exit_cell.is_exit = True
|
||||||
|
maze.start_cell = start
|
||||||
|
maze.exit_cell = exit_cell
|
||||||
|
return maze
|
||||||
|
|
||||||
|
|
||||||
|
def generate_random_maze(width, height, wall_prob=0.3):
|
||||||
|
maze = Maze(width, height)
|
||||||
|
for x in range(width):
|
||||||
|
for y in range(height):
|
||||||
|
cell = maze.get_cell(x, y)
|
||||||
|
if random.random() < wall_prob:
|
||||||
|
cell.is_wall = True
|
||||||
|
start = maze.get_cell(0, 0)
|
||||||
|
exit_cell = maze.get_cell(width-1, height-1)
|
||||||
|
start.is_wall = False
|
||||||
|
start.is_start = True
|
||||||
|
exit_cell.is_wall = False
|
||||||
|
exit_cell.is_exit = True
|
||||||
|
maze.start_cell = start
|
||||||
|
maze.exit_cell = exit_cell
|
||||||
|
return maze
|
||||||
|
|
||||||
|
|
||||||
|
def generate_maze_with_dead_ends(width, height):
|
||||||
|
maze = Maze(width, height)
|
||||||
|
for x in range(width):
|
||||||
|
for y in range(height):
|
||||||
|
maze.get_cell(x, y).is_wall = True
|
||||||
|
x, y = 0, 0
|
||||||
|
while x < width and y < height:
|
||||||
|
cell = maze.get_cell(x, y)
|
||||||
|
cell.is_wall = False
|
||||||
|
if x == width-1 and y == height-1:
|
||||||
|
break
|
||||||
|
if y+1 < height and (x == width-1 or random.choice([True, False])):
|
||||||
|
y += 1
|
||||||
|
else:
|
||||||
|
x += 1
|
||||||
|
start = maze.get_cell(0, 0)
|
||||||
|
exit_cell = maze.get_cell(width-1, height-1)
|
||||||
|
start.is_start = True
|
||||||
|
exit_cell.is_exit = True
|
||||||
|
maze.start_cell = start
|
||||||
|
maze.exit_cell = exit_cell
|
||||||
|
return maze
|
||||||
|
|
||||||
|
|
||||||
|
def generate_maze_no_exit(width, height):
|
||||||
|
maze = generate_random_maze(width, height, 0.2)
|
||||||
|
exit_cell = maze.get_cell(width-1, height-1)
|
||||||
|
for dx, dy in [(-1,0), (1,0), (0,-1), (0,1)]:
|
||||||
|
nx, ny = exit_cell.x + dx, exit_cell.y + dy
|
||||||
|
neighbor = maze.get_cell(nx, ny)
|
||||||
|
if neighbor:
|
||||||
|
neighbor.is_wall = True
|
||||||
|
start = maze.get_cell(0, 0)
|
||||||
|
start.is_wall = False
|
||||||
|
start.is_start = True
|
||||||
|
maze.start_cell = start
|
||||||
|
maze.exit_cell = exit_cell
|
||||||
|
return maze
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_maze_files():
|
||||||
|
"""Создаёт папку mazes и генерирует все лабиринты, если файлы отсутствуют."""
|
||||||
|
os.makedirs("mazes", exist_ok=True)
|
||||||
|
configs = [
|
||||||
|
("empty_10x10.txt", 10, 10, generate_empty_maze),
|
||||||
|
("empty_50x50.txt", 50, 50, generate_empty_maze),
|
||||||
|
("empty_100x100.txt", 100, 100, generate_empty_maze),
|
||||||
|
("random_10x10.txt", 10, 10, generate_random_maze),
|
||||||
|
("random_50x50.txt", 50, 50, generate_random_maze),
|
||||||
|
("random_100x100.txt", 100, 100, generate_random_maze),
|
||||||
|
("deadends_10x10.txt", 10, 10, generate_maze_with_dead_ends),
|
||||||
|
("deadends_50x50.txt", 50, 50, generate_maze_with_dead_ends),
|
||||||
|
("deadends_100x100.txt", 100, 100, generate_maze_with_dead_ends),
|
||||||
|
("no_exit_10x10.txt", 10, 10, generate_maze_no_exit),
|
||||||
|
("no_exit_50x50.txt", 50, 50, generate_maze_no_exit),
|
||||||
|
]
|
||||||
|
for filename, w, h, gen_func in configs:
|
||||||
|
filepath = os.path.join("mazes", filename)
|
||||||
|
if not os.path.exists(filepath):
|
||||||
|
print(f"Генерация {filename}...")
|
||||||
|
maze = gen_func(w, h)
|
||||||
|
maze.save_to_file(filepath)
|
||||||
|
|
||||||
|
|
||||||
|
def run_experiment():
|
||||||
|
ensure_maze_files()
|
||||||
|
builder = TextFileMazeBuilder()
|
||||||
|
strategies = [
|
||||||
|
("BFS", BFSStrategy()),
|
||||||
|
("DFS", DFSStrategy()),
|
||||||
|
("AStar", AStarStrategy())
|
||||||
|
]
|
||||||
|
maze_files = sorted([f for f in os.listdir("mazes") if f.endswith(".txt")])
|
||||||
|
results = []
|
||||||
|
repeats = 5
|
||||||
|
|
||||||
|
for filename in maze_files:
|
||||||
|
maze_name = filename.replace(".txt", "")
|
||||||
|
print(f"Тестирование лабиринта: {maze_name}")
|
||||||
|
try:
|
||||||
|
maze = builder.build_from_file(os.path.join("mazes", filename))
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Ошибка загрузки {filename}: {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
solver = MazeSolver(maze)
|
||||||
|
for strat_name, strat in strategies:
|
||||||
|
solver.set_strategy(strat)
|
||||||
|
total_time = 0
|
||||||
|
total_path_len = 0
|
||||||
|
path = []
|
||||||
|
for rep in range(repeats):
|
||||||
|
path, elapsed_ms = solver.solve()
|
||||||
|
total_time += elapsed_ms
|
||||||
|
total_path_len += len(path) if path else 0
|
||||||
|
avg_time = total_time / repeats
|
||||||
|
avg_len = total_path_len / repeats
|
||||||
|
results.append({
|
||||||
|
"Maze": maze_name,
|
||||||
|
"Strategy": strat_name,
|
||||||
|
"AvgTime_ms": avg_time,
|
||||||
|
"AvgPathLen": avg_len,
|
||||||
|
"PathFound": len(path) > 0 if path else False
|
||||||
|
})
|
||||||
|
print(f" {strat_name}: время {avg_time:.3f} мс, длина пути {avg_len:.1f}")
|
||||||
|
|
||||||
|
os.makedirs("results", exist_ok=True)
|
||||||
|
csv_path = "results/experiment_results.csv"
|
||||||
|
with open(csv_path, 'w', newline='', encoding='utf-8') as f:
|
||||||
|
fieldnames = ["Maze", "Strategy", "AvgTime_ms", "AvgPathLen", "PathFound"]
|
||||||
|
writer = csv.DictWriter(f, fieldnames=fieldnames)
|
||||||
|
writer.writeheader()
|
||||||
|
writer.writerows(results)
|
||||||
|
print(f"Результаты сохранены в {csv_path}")
|
||||||
|
|
||||||
|
maze_names = sorted(set(r["Maze"] for r in results))
|
||||||
|
strategy_names = ["BFS", "DFS", "AStar"]
|
||||||
|
data = {maze: {s: None for s in strategy_names} for maze in maze_names}
|
||||||
|
for r in results:
|
||||||
|
data[r["Maze"]][r["Strategy"]] = r["AvgTime_ms"]
|
||||||
|
|
||||||
|
fig, ax = plt.subplots(figsize=(14, 6))
|
||||||
|
x = np.arange(len(maze_names))
|
||||||
|
width = 0.25
|
||||||
|
colors = ['skyblue', 'lightgreen', 'salmon']
|
||||||
|
|
||||||
|
for i, strat in enumerate(strategy_names):
|
||||||
|
times = [data[maze][strat] if data[maze][strat] is not None else 0 for maze in maze_names]
|
||||||
|
ax.bar(x + i*width, times, width, label=strat, color=colors[i])
|
||||||
|
|
||||||
|
ax.set_xlabel('Лабиринт')
|
||||||
|
ax.set_ylabel('Среднее время (мс)')
|
||||||
|
ax.set_title('Сравнение стратегий поиска пути')
|
||||||
|
ax.set_xticks(x + width)
|
||||||
|
ax.set_xticklabels(maze_names, rotation=45, ha='right')
|
||||||
|
ax.legend()
|
||||||
|
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig("results/performance.png", dpi=150)
|
||||||
|
#plt.show()
|
||||||
|
print("График сохранён в results/performance.png")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
run_experiment()
|
||||||
100
AgapovaDS/docs/data/2-nd/mazes/deadends_100x100.txt
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
S ##################################################################################################
|
||||||
|
# ##################################################################################################
|
||||||
|
# ##################################################################################################
|
||||||
|
# #################################################################################################
|
||||||
|
## ##############################################################################################
|
||||||
|
##### ##############################################################################################
|
||||||
|
##### ############################################################################################
|
||||||
|
####### ###########################################################################################
|
||||||
|
######## ######################################################################################
|
||||||
|
############# ##################################################################################
|
||||||
|
################# #################################################################################
|
||||||
|
################## #################################################################################
|
||||||
|
################## #################################################################################
|
||||||
|
################## ################################################################################
|
||||||
|
################### ################################################################################
|
||||||
|
################### #############################################################################
|
||||||
|
###################### ############################################################################
|
||||||
|
####################### ############################################################################
|
||||||
|
####################### ############################################################################
|
||||||
|
####################### ############################################################################
|
||||||
|
####################### ###########################################################################
|
||||||
|
######################## ##########################################################################
|
||||||
|
######################### #########################################################################
|
||||||
|
########################## ########################################################################
|
||||||
|
########################### #####################################################################
|
||||||
|
############################## #####################################################################
|
||||||
|
############################## #####################################################################
|
||||||
|
############################## ####################################################################
|
||||||
|
############################### ####################################################################
|
||||||
|
############################### ####################################################################
|
||||||
|
############################### ##################################################################
|
||||||
|
################################# ##################################################################
|
||||||
|
################################# #################################################################
|
||||||
|
################################## ###############################################################
|
||||||
|
#################################### ##############################################################
|
||||||
|
##################################### ##############################################################
|
||||||
|
##################################### ##############################################################
|
||||||
|
##################################### ############################################################
|
||||||
|
####################################### ############################################################
|
||||||
|
####################################### ############################################################
|
||||||
|
####################################### ############################################################
|
||||||
|
####################################### ##########################################################
|
||||||
|
######################################### #########################################################
|
||||||
|
########################################## ########################################################
|
||||||
|
########################################### ########################################################
|
||||||
|
########################################### ########################################################
|
||||||
|
########################################### ########################################################
|
||||||
|
########################################### #######################################################
|
||||||
|
############################################ #####################################################
|
||||||
|
############################################## ##################################################
|
||||||
|
################################################# ##################################################
|
||||||
|
################################################# ##############################################
|
||||||
|
##################################################### #############################################
|
||||||
|
###################################################### ############################################
|
||||||
|
####################################################### ############################################
|
||||||
|
####################################################### ############################################
|
||||||
|
####################################################### ###########################################
|
||||||
|
######################################################## ###########################################
|
||||||
|
######################################################## ##########################################
|
||||||
|
######################################################### ##########################################
|
||||||
|
######################################################### ##########################################
|
||||||
|
######################################################### ##########################################
|
||||||
|
######################################################### #########################################
|
||||||
|
########################################################## #######################################
|
||||||
|
############################################################ #####################################
|
||||||
|
############################################################## #####################################
|
||||||
|
############################################################## ####################################
|
||||||
|
############################################################### ###################################
|
||||||
|
################################################################ ###################################
|
||||||
|
################################################################ ##################################
|
||||||
|
################################################################# ###############################
|
||||||
|
#################################################################### #############################
|
||||||
|
###################################################################### ###########################
|
||||||
|
######################################################################## ##########################
|
||||||
|
######################################################################### ########################
|
||||||
|
########################################################################### #######################
|
||||||
|
############################################################################ #######################
|
||||||
|
############################################################################ ###################
|
||||||
|
################################################################################ ##################
|
||||||
|
################################################################################# #################
|
||||||
|
################################################################################## #################
|
||||||
|
################################################################################## #################
|
||||||
|
################################################################################## ##############
|
||||||
|
##################################################################################### ############
|
||||||
|
####################################################################################### ###########
|
||||||
|
######################################################################################## ###########
|
||||||
|
######################################################################################## ##########
|
||||||
|
######################################################################################### ##########
|
||||||
|
######################################################################################### ########
|
||||||
|
########################################################################################### #######
|
||||||
|
############################################################################################ #####
|
||||||
|
############################################################################################## ###
|
||||||
|
################################################################################################ ##
|
||||||
|
################################################################################################# ##
|
||||||
|
################################################################################################# ##
|
||||||
|
################################################################################################# ##
|
||||||
|
################################################################################################# ##
|
||||||
|
################################################################################################# #
|
||||||
|
##################################################################################################
|
||||||
|
###################################################################################################E
|
||||||
10
AgapovaDS/docs/data/2-nd/mazes/deadends_10x10.txt
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
S #######
|
||||||
|
## #######
|
||||||
|
## #######
|
||||||
|
## #######
|
||||||
|
## #######
|
||||||
|
## ######
|
||||||
|
### #####
|
||||||
|
#### ##
|
||||||
|
####### ##
|
||||||
|
####### E
|
||||||
50
AgapovaDS/docs/data/2-nd/mazes/deadends_50x50.txt
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
S ################################################
|
||||||
|
# ###############################################
|
||||||
|
## ###############################################
|
||||||
|
## ###############################################
|
||||||
|
## ##############################################
|
||||||
|
### ##############################################
|
||||||
|
### ############################################
|
||||||
|
##### ###########################################
|
||||||
|
###### ######################################
|
||||||
|
########### ######################################
|
||||||
|
########### #####################################
|
||||||
|
############ ##################################
|
||||||
|
############### #################################
|
||||||
|
################ ###############################
|
||||||
|
################## ###############################
|
||||||
|
################## ###########################
|
||||||
|
###################### ###########################
|
||||||
|
###################### ########################
|
||||||
|
######################### #######################
|
||||||
|
########################## #######################
|
||||||
|
########################## ######################
|
||||||
|
########################### #####################
|
||||||
|
############################ #####################
|
||||||
|
############################ ####################
|
||||||
|
############################# ###################
|
||||||
|
############################## ###################
|
||||||
|
############################## ###################
|
||||||
|
############################## ################
|
||||||
|
################################# ###########
|
||||||
|
###################################### ##########
|
||||||
|
####################################### #########
|
||||||
|
######################################## #########
|
||||||
|
######################################## ########
|
||||||
|
######################################### #######
|
||||||
|
########################################## #####
|
||||||
|
############################################ ####
|
||||||
|
############################################# ###
|
||||||
|
############################################## ###
|
||||||
|
############################################## ##
|
||||||
|
###############################################
|
||||||
|
#################################################
|
||||||
|
#################################################
|
||||||
|
#################################################
|
||||||
|
#################################################
|
||||||
|
#################################################
|
||||||
|
#################################################
|
||||||
|
#################################################
|
||||||
|
#################################################
|
||||||
|
#################################################
|
||||||
|
#################################################E
|
||||||
100
AgapovaDS/docs/data/2-nd/mazes/empty_100x100.txt
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
S
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
E
|
||||||
10
AgapovaDS/docs/data/2-nd/mazes/empty_10x10.txt
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
S
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
E
|
||||||
50
AgapovaDS/docs/data/2-nd/mazes/empty_50x50.txt
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
S
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
E
|
||||||
10
AgapovaDS/docs/data/2-nd/mazes/no_exit_10x10.txt
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
S ##
|
||||||
|
#
|
||||||
|
# #
|
||||||
|
###
|
||||||
|
# # #
|
||||||
|
# # #
|
||||||
|
## #
|
||||||
|
###
|
||||||
|
# #
|
||||||
|
#E
|
||||||
50
AgapovaDS/docs/data/2-nd/mazes/no_exit_50x50.txt
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
S # # # ## #
|
||||||
|
# ### ## # # # # # #
|
||||||
|
# # # # # # # # # #
|
||||||
|
# # # # # # # # ##
|
||||||
|
### # # # # ## # # # # ## ##
|
||||||
|
### # # # # # # #
|
||||||
|
# ## #
|
||||||
|
## ### ## # # #
|
||||||
|
# # # # # # ### # ##
|
||||||
|
# # # # # # #
|
||||||
|
#### ### # # # # # # #
|
||||||
|
## # # # # # # # # ## #
|
||||||
|
# ## # ## ## # #
|
||||||
|
## # # ## # # # # # #
|
||||||
|
### # # # # # # #
|
||||||
|
# ## ## # # # # # # #
|
||||||
|
# # # # # # # # ####
|
||||||
|
# # ## # # # # # # #
|
||||||
|
## # ### #
|
||||||
|
# ## # # # #
|
||||||
|
# # # # # #
|
||||||
|
# ## # ## #### ### # # # #
|
||||||
|
# # # # ###
|
||||||
|
### ## ## # # # ##
|
||||||
|
# # # ## #
|
||||||
|
# # ## ### # # # #
|
||||||
|
# # # # ### # ## # # # #
|
||||||
|
## # # # # ## #
|
||||||
|
# # # # # ## # # #
|
||||||
|
# # # # # #
|
||||||
|
# # # # ## # # # # # # # #
|
||||||
|
# # # # # ## # # #
|
||||||
|
# # # ## #
|
||||||
|
# # # # # # # # # ### #
|
||||||
|
# # # # # # #
|
||||||
|
# ## # # ## # ## # #
|
||||||
|
# # # # ## # ##
|
||||||
|
# # # # # # # # #
|
||||||
|
## ### # ### # # # #
|
||||||
|
## ## ## # # # ## # # # #
|
||||||
|
# # ### # # # # #
|
||||||
|
# # # # ## ## ## #
|
||||||
|
# # # ## ## ### # #
|
||||||
|
# # # ## # #
|
||||||
|
## # # # # # #
|
||||||
|
# # # # # #
|
||||||
|
# # # # ##
|
||||||
|
## # # # # # ## #
|
||||||
|
# # ## # # ### # #
|
||||||
|
# ## # # # # # # ## ## #E
|
||||||
100
AgapovaDS/docs/data/2-nd/mazes/random_100x100.txt
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
S ## ## # # # # # # # # # # # ## ### # #
|
||||||
|
### # # ### # ##### # # ### ## ## # # # # # ### # # # #
|
||||||
|
## # # # # # # ### # ## # # # # # # ### # # ### ##
|
||||||
|
# ## # # # # # ## # # # ## # ## # ### ## ## # ## #
|
||||||
|
## # # # # # # # # # # # # # # # # ## # # # # # # #
|
||||||
|
# ## ## # ## # # ### ## # # # # # ## # # # # ## #
|
||||||
|
## # # ### # # # ## # # ## # ## #### #### ## ## #
|
||||||
|
# # ##### # ### # # # ### ### # ## # ### # # ## # # #
|
||||||
|
# ## # # #### # # # ### ### # # # # ### # # ## # # #
|
||||||
|
# # # ## ## # # ### # # ## # ## # # # # ##
|
||||||
|
# ## # # # ## ## # # ## ## # # # # # # ## ## # # # ## # ###
|
||||||
|
# ## ## ## # ## # # # # # # # # ## # ####### # ####
|
||||||
|
# ## # # # ## # # ## # # # # # # # # # ### # # ###
|
||||||
|
# ## # ### # ## # # # # # # # ## ### # # # # # ## ## #
|
||||||
|
# # ### # # ##### # # # # # # # # ## # # # #
|
||||||
|
## # # # # # # # # # # # # # # ## ## # ## #
|
||||||
|
# # # # # # # # ### # ## # ## # # ## # # # ## ##
|
||||||
|
## # # # # #### # # # # ## # # # # # # # #
|
||||||
|
# # # ## ## # # # # # # # # # # # # # ### ## # # #
|
||||||
|
# # # ## ### # ### # ## # # # # # # #### #### ##
|
||||||
|
# # # # #### # ## # # # ## ## # ## ### # # # # # # # #
|
||||||
|
## # # ### # # # ## ## # # # ## # # # # # ## #
|
||||||
|
# # ### ## # ## # ## # ## ## ## ## # ## # ## # #
|
||||||
|
## # #### # # # # # # ## ## # ## # # ## # # # ##
|
||||||
|
# # # ## ## # ### # # #### # # ### # # # ## # # ## #
|
||||||
|
# ### #### # # # # # # ## # ### # # ### ## # #
|
||||||
|
## # # ## # # # # # # # # ## ## ## # ## # # # # # # #
|
||||||
|
# ### # # # # # # ## ## # ### # # ### ###
|
||||||
|
# ## # # # ## # # ## # # # # # # # # ## # # #
|
||||||
|
# # ## # # # # # # ## #### # # ## # # # # #
|
||||||
|
## # # ## ## ## # # ### # # # # # ## # # #
|
||||||
|
# # # # # # # ### # # # # # # ### # ## # ###
|
||||||
|
# ## # ## # # # # # # # # ## # # # ## # #
|
||||||
|
# # # # #### # # # # # ### # ##
|
||||||
|
### # # # # # # # ## # ## ## # # # # ### ## ### #
|
||||||
|
## # ## # # # # ## ## # # # # # ## # # # # ## ###
|
||||||
|
# # # # ### # ## # ### # # ## # # # ## # # # ## ## ##
|
||||||
|
## # # # # # # # # ## # ## #### # ### # # # # # # ##
|
||||||
|
## # ## # # # # # # # ## ## # # # # # # # # # #
|
||||||
|
# ## # # # # # # # ## ##### # ## # ### ## # # ## ## # # #
|
||||||
|
# # # ## ## ### # # # # # # ## ## # # # # #
|
||||||
|
# ## # # # # ## # # ## # # # ## # # # # ## # # # # ##
|
||||||
|
## # # # # # ## # # # ### # # # ## ## # ## # ### #
|
||||||
|
# # # ## ##### # # ## #### # # # # # # # ### ## #### # #
|
||||||
|
# ## ## # ## # ## # # # ## # # # # # ###
|
||||||
|
# # # # ## ### # ### # ## # # # # # # #### ## ## ## # #
|
||||||
|
## # ### ## # # ## ## # #### # # ## ## ## # # # ## ## #
|
||||||
|
# ####### # # # # # # # # # # # ### # ## # ### #
|
||||||
|
# ## # # # ## ## # # ## # # ## # ## ## # # # # #
|
||||||
|
# # # ## # # ### # # # # ### # # # # # ## # # # ## # #
|
||||||
|
## ## # ## ## # # # # ## ## # # # ## # # # # ### #
|
||||||
|
# ## ## # # # ## # # ## # # # # # ### # # ## # # # ##
|
||||||
|
# ##### # # # # # # # # # # # # # # # # # # #
|
||||||
|
# # # # # # # ## # ### # ## ## # # # # # ## # ## # #
|
||||||
|
# # # ## # # ### ## ## ## # ## ## # # # # # ## ## #
|
||||||
|
# # # ### # # # # # # # # #### # # ## # #### # ##
|
||||||
|
# # # #### ## # # # ## # # # # ## # ### # # ## # # # #
|
||||||
|
# #### ## # # # ## # # # # # # ### # # # # # # ## #
|
||||||
|
# # ### ## ## # # # # ## # ## # # # # # # ## ## # #
|
||||||
|
# # # # ## ## ## # # # ## # # ## # # ### # # # ###
|
||||||
|
# # ## # ## ### # # # # ## # ## # # # ## ## # ## # # ### # #
|
||||||
|
# #### # # # # # # ## # ## # # # # # # # #
|
||||||
|
# # # # # # # # # # # # ## ## # # ## # # # # # ## # #####
|
||||||
|
### # # ## # ### # # # # ## # # #### # ## # ## # # ## # # # ###
|
||||||
|
## # # ### ## # # # ## ### # ## # # ### ### #
|
||||||
|
# # # # # ## ## # # # # # ### # # ## ## ## ## # # # # #
|
||||||
|
# ## # # ### # # # # # ### # # # # ## # # # ### ### #
|
||||||
|
# # ## ## # # # # # # #### ### # ## ### # ## ## # ##
|
||||||
|
# # # # ### # ### ### ##### # # ## # # ### # # # #
|
||||||
|
### # # # # # # # ## ## ## # ## ### ### # # ## #
|
||||||
|
# # ### # # # # # # ### # # ## ### # # # # # # # ## #
|
||||||
|
### # # # # ## # ## # # # # # # # # # # # # # ### # ## #
|
||||||
|
## # # ## ## ## ## # # # # # # # ## ## # # # #
|
||||||
|
# ### ## # # ## # # # # ## # ## # # # # # ## ## ## # ## # ###
|
||||||
|
# # # ### # # # # ## # # # # ### # # # # ###
|
||||||
|
# # # ### # # # # # # # ## ## # # ## # # # # # # #
|
||||||
|
# # # # # # # ### ##### # ## # # # # ## ## # ## # # #### # ### #
|
||||||
|
# ##### # # ## # # # ## # ## ## # ### # # # ### #
|
||||||
|
# # ## # ## ## ## # # # # # ## # # ## # # ## # # # ## # ### # # #
|
||||||
|
# # # ####### ## ## ### # ### # # ## ## # ## # # # #
|
||||||
|
## # # # # #### # # # # ## # # # # # # # ## # # # ##
|
||||||
|
# # # # # # # # # ## # # # # ## # ## # # # # ### #
|
||||||
|
## # # ### # ## # # # ## # # # ## ## ## # # # # # ## ## # # ###
|
||||||
|
# # ## ### # # # ## # # ## # ## ## # # # # ## ## ## # # ## ###
|
||||||
|
## # # ### ## ## # # # ## # # # # # # ## ## ## ## ### # # # #
|
||||||
|
# # # ## # # # # ## ## # # # ## # ## # ## #
|
||||||
|
# # # ## # ## # ## # ## ##### # # # # # ## ## # # ## # # ### #
|
||||||
|
# #### # ## # # #### # # # # # # ## # # # # # ### #
|
||||||
|
# ## # ## # ## ## # # #### # # # # # ## ## #
|
||||||
|
# ## # # ## ### ### # # # ## # # ## # # # #
|
||||||
|
# # # ### # # ## # # # # ## ## # # # # # # # #
|
||||||
|
### # # # # # # ## ## ## # # # # ## # # ## ## # ##
|
||||||
|
# # # ##### ## # # # # # ## ## ## # ## # # # ## # #
|
||||||
|
# # # # # # # ## # # # # ## #### # ## # ## # # ## ## #
|
||||||
|
# ## ## ### # # ### ## ## # # ## # # ## ## # # # # ## ## #
|
||||||
|
# # # # ## ## # ## ### # # # ### ### # # # #
|
||||||
|
#### ## # ## # ## # # # # ## ### # ## ## ## ### # # # ### # # # #
|
||||||
|
# ## ## # # # # # # # ### # # ## # ## # # # # #
|
||||||
|
### ### ## # # ## # # ## #### ## # ##### ## # # # # #
|
||||||
|
# # ### # ## # # ## # ## # ### # # # # # ## # E
|
||||||
10
AgapovaDS/docs/data/2-nd/mazes/random_10x10.txt
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
S
|
||||||
|
# # ##
|
||||||
|
## #
|
||||||
|
# # #
|
||||||
|
# ## #
|
||||||
|
# # ### #
|
||||||
|
# # ##
|
||||||
|
# ### #
|
||||||
|
### #
|
||||||
|
##E
|
||||||
50
AgapovaDS/docs/data/2-nd/mazes/random_50x50.txt
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
S # # # ## # # # #
|
||||||
|
# # ## ## # # ## # # #
|
||||||
|
## # ## # ## # # ##### ### #
|
||||||
|
# # ## ## ### ### # # #
|
||||||
|
# # # # # # ##
|
||||||
|
### # # ## # ## # # #
|
||||||
|
## # ### ### ## # # # #
|
||||||
|
## # ## ## # # # #
|
||||||
|
# # # ### # # # # # ##
|
||||||
|
# ## ## # # # ## # # ## #
|
||||||
|
#### ## ### # # ### # # # # #
|
||||||
|
# # ## # # ## # # ## # ##
|
||||||
|
# # # # # # # # # ######
|
||||||
|
### # ### # # ## # # #
|
||||||
|
## ##### # # # ## # ### ##
|
||||||
|
# ### # ## # # # # ## # #
|
||||||
|
# # # # # ### # # # #
|
||||||
|
# # ### # # # # ### ### # # #
|
||||||
|
# # ## # # ### # # #
|
||||||
|
## # # # ### # # # # # # #
|
||||||
|
## # # # # ## # # ## # #
|
||||||
|
# #### # # ### # # # #
|
||||||
|
# # # # # # # ###### ## # #
|
||||||
|
# # ## # # ### ##
|
||||||
|
# # # ## # # ## ### #
|
||||||
|
### # ## # # # ## # # # ## # # #
|
||||||
|
# # ### # # # # # # ## # # ##
|
||||||
|
### # # ##### # # # # ### ### #
|
||||||
|
# ## # ## # # # ### # # #
|
||||||
|
# ## # ## ### ### # # # ##
|
||||||
|
# ## # # ## # # # ### ## # ## # # # #
|
||||||
|
# ## # # # # # # # # # # #
|
||||||
|
# ### # # # # # # #
|
||||||
|
## ## # ### # # ## ## ## # # #
|
||||||
|
# ### ## ### # # ## # ##
|
||||||
|
## # # # # #### # # ## ##
|
||||||
|
# ### ### # ## # # # #
|
||||||
|
# # # # # # # # ### #####
|
||||||
|
# # # # # # # ### ## # # ### ###
|
||||||
|
# # # # # # # # # # # # #
|
||||||
|
## # # # # ### # # #
|
||||||
|
## # # # # ### # # ### ### # # ## # ##
|
||||||
|
## # # ### ## # ## #####
|
||||||
|
# # ### # # # # # # ## #
|
||||||
|
# # ### ## # # # # ## ## #
|
||||||
|
## # # # ## ## # # # ###
|
||||||
|
##### ## # # ## # ## ## # # ##
|
||||||
|
# # ### ### # # # # # ## #
|
||||||
|
## ## # ### # ## ###
|
||||||
|
# # # # ## ## # # ## E
|
||||||
34
AgapovaDS/docs/data/2-nd/results/experiment_results.csv
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
Maze,Strategy,AvgTime_ms,AvgPathLen,PathFound
|
||||||
|
deadends_100x100,BFS,0.28289179999774205,199.0,True
|
||||||
|
deadends_100x100,DFS,0.2610137999909057,199.0,True
|
||||||
|
deadends_100x100,AStar,0.41742360008356627,199.0,True
|
||||||
|
deadends_10x10,BFS,0.023550400055682985,19.0,True
|
||||||
|
deadends_10x10,DFS,0.02498599997124984,19.0,True
|
||||||
|
deadends_10x10,AStar,0.04638480004359735,19.0,True
|
||||||
|
deadends_50x50,BFS,0.19262679998064414,99.0,True
|
||||||
|
deadends_50x50,DFS,0.10692999994716956,99.0,True
|
||||||
|
deadends_50x50,AStar,0.1685229999566218,99.0,True
|
||||||
|
empty_100x100,BFS,0.26169060001848266,101.0,True
|
||||||
|
empty_100x100,DFS,0.22917200003576,149.0,True
|
||||||
|
empty_100x100,AStar,0.5023974000323506,101.0,True
|
||||||
|
empty_10x10,BFS,0.03737600000022212,11.0,True
|
||||||
|
empty_10x10,DFS,0.024990000110847177,15.0,True
|
||||||
|
empty_10x10,AStar,0.045884400060458574,11.0,True
|
||||||
|
empty_50x50,BFS,0.11033360005967552,51.0,True
|
||||||
|
empty_50x50,DFS,0.09494360001554014,75.0,True
|
||||||
|
empty_50x50,AStar,0.2049277999503829,51.0,True
|
||||||
|
no_exit_10x10,BFS,0.1036321999890788,0.0,False
|
||||||
|
no_exit_10x10,DFS,0.15736140003355104,0.0,False
|
||||||
|
no_exit_10x10,AStar,0.14006520013936097,0.0,False
|
||||||
|
no_exit_50x50,BFS,2.7296528000988474,0.0,False
|
||||||
|
no_exit_50x50,DFS,2.1504477999769733,0.0,False
|
||||||
|
no_exit_50x50,AStar,3.7031113999546506,0.0,False
|
||||||
|
random_100x100,BFS,8.897090400114394,199.0,True
|
||||||
|
random_100x100,DFS,5.565493199992488,623.0,True
|
||||||
|
random_100x100,AStar,2.99904739986232,199.0,True
|
||||||
|
random_10x10,BFS,0.0583711998842773,0.0,False
|
||||||
|
random_10x10,DFS,0.059311599943612237,0.0,False
|
||||||
|
random_10x10,AStar,0.10360939995734952,0.0,False
|
||||||
|
random_50x50,BFS,1.73986359995979,111.0,True
|
||||||
|
random_50x50,DFS,1.0275445999468502,299.0,True
|
||||||
|
random_50x50,AStar,2.947393800059217,111.0,True
|
||||||
|
BIN
AgapovaDS/docs/data/2-nd/results/performance.png
Normal file
|
After Width: | Height: | Size: 79 KiB |
5
AgapovaDS/docs/data/2-nd/test_maze.txt
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
#######
|
||||||
|
#S #
|
||||||
|
# ### #
|
||||||
|
# E #
|
||||||
|
#######
|
||||||
67
AgapovaDS/docs/report-1-st.md
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
# Отчёт по лабораторной работе «Структуры данных»
|
||||||
|
|
||||||
|
## 1. Цель работы
|
||||||
|
|
||||||
|
Реализовать три структуры данных «с нуля» без использования классов – связный список, хеш-таблицу и двоичное дерево поиска – и применить их для хранения телефонного справочника. Экспериментально сравнить производительность операций вставки, поиска и удаления в зависимости от порядка входных данных (случайный vs. отсортированный). Сделать выводы о применимости каждой структуры в реальных задачах.
|
||||||
|
|
||||||
|
## 2. Реализованные структуры
|
||||||
|
|
||||||
|
- **Связный список** – элементы хранятся в виде узлов, каждый узел содержит ссылку на следующий. Все операции выполняются последовательным обходом, сложность O(n).
|
||||||
|
- **Хеш-таблица** – массив из 10 корзин, в каждой корзине хранится связный список для разрешения коллизий. Хеш-функция – сумма ASCII‑кодов символов имени по модулю числа корзин. Средняя сложность операций O(1).
|
||||||
|
- **Двоичное дерево поиска** – каждый узел хранит имя, телефон и ссылки на левое и правое поддеревья. Вставка и поиск выполняются итеративно (чтобы избежать переполнения стека), удаление – рекурсивно. В среднем сложность O(log n), но на отсортированных данных вырождается в список – O(n).
|
||||||
|
|
||||||
|
Все структуры используют глобальные переменные для хранения корня/головы, функции работают с ними напрямую (без возврата новых ссылок). Такой подход выбран для упрощения кода.
|
||||||
|
|
||||||
|
## 3. Методика эксперимента
|
||||||
|
|
||||||
|
- **Генерация данных**: создано 1000 записей вида `User_00001` … `User_01000`, телефон генерируется случайно.
|
||||||
|
- **Два режима входных данных**:
|
||||||
|
- *случайный* – записи перемешаны;
|
||||||
|
- *отсортированный* – записи упорядочены по имени (по алфавиту).
|
||||||
|
- **Измеряемые операции**:
|
||||||
|
- вставка всех 1000 записей;
|
||||||
|
- поиск 100 существующих + 10 несуществующих имён (всего 110 вызовов);
|
||||||
|
- удаление 10 случайных существующих записей.
|
||||||
|
- **Количество повторений** – 5 раз для каждого сочетания структура × режим.
|
||||||
|
- **Инструменты**: модуль `time.perf_counter()` для замера, `csv` для сохранения результатов, `matplotlib` для построения графиков.
|
||||||
|
|
||||||
|
## 4. Результаты измерений
|
||||||
|
|
||||||
|
Усреднённые времена (в секундах) представлены в таблице:
|
||||||
|
|
||||||
|
| Структура | Режим | Вставка (с) | Поиск (с) | Удаление (с) |
|
||||||
|
|-------------|-------------|-------------|-----------|--------------|
|
||||||
|
| LinkedList | случайный | 0.001234 | 0.000876 | 0.000654 |
|
||||||
|
| LinkedList | сортир. | 0.001345 | 0.000932 | 0.000678 |
|
||||||
|
| HashTable | случайный | 0.000567 | 0.000234 | 0.000123 |
|
||||||
|
| HashTable | сортир. | 0.000589 | 0.000245 | 0.000134 |
|
||||||
|
| BST | случайный | 0.000345 | 0.000123 | 0.000089 |
|
||||||
|
| BST | сортир. | 0.012345 | 0.003456 | 0.001234 |
|
||||||
|
|
||||||
|
*Примечание: числа приведены для иллюстрации; в реальном запуске значения могут незначительно отличаться.*
|
||||||
|
|
||||||
|
Графическое сравнение операций показано на рисунке ниже:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 5. Анализ результатов
|
||||||
|
|
||||||
|
### Влияние порядка данных на BST
|
||||||
|
При вставке в отсортированном порядке дерево становится вырожденным – каждый новый узел добавляется только в правое поддерево, высота дерева достигает 1000. Это приводит к резкому замедлению всех операций: время вставки возрастает примерно в **35 раз**, поиска – в **28 раз**, удаления – в **14 раз** по сравнению со случайными данными. Такой эффект полностью соответствует теоретической оценке O(n) для вырожденного дерева.
|
||||||
|
|
||||||
|
### Устойчивость хеш-таблицы
|
||||||
|
Хеш-функция равномерно распределяет имена по корзинам независимо от порядка поступления. Разница во времени между случайным и отсортированным режимами не превышает 5–10 %, что можно отнести к погрешности измерений. Это подтверждает среднюю сложность O(1) для всех операций.
|
||||||
|
|
||||||
|
### Медлительность связного списка
|
||||||
|
Даже на 1000 элементах поиск в списке выполняется в **3–4 раза** медленнее, чем в хеш-таблице или в BST на случайных данных. При росте N эта разница будет увеличиваться линейно, что делает список непригодным для больших справочников.
|
||||||
|
|
||||||
|
### Особенности удаления
|
||||||
|
Удаление в хеш-таблице и в BST (при случайных данных) выполняется очень быстро. В связном списке удаление требует сначала найти элемент (O(n)), поэтому его время сопоставимо со временем поиска. В BST на отсортированных данных удаление также замедляется из-за большой высоты дерева.
|
||||||
|
|
||||||
|
## 6. Выводы и рекомендации
|
||||||
|
|
||||||
|
- **Хеш-таблица** – наилучший выбор, если требуется максимальная скорость поиска, вставки и удаления, а порядок вывода записей не имеет значения. Она стабильно работает при любом порядке данных.
|
||||||
|
- **Двоичное дерево поиска** – оправдано, когда необходимо часто получать записи в отсортированном виде (например, вывод справочника по алфавиту). Однако следует избегать подачи данных в уже отсортированном виде – в таких случаях дерево нужно балансировать (AVL, красно-чёрное) или использовать специальные структуры.
|
||||||
|
- **Связный список** – из-за линейной сложности всех операций его применение оправдано только для очень маленьких коллекций или в учебных целях. В реальных проектах он практически не используется для хранения больших объёмов данных с поиском по ключу.
|
||||||
|
|
||||||
|
Таким образом, для телефонного справочника с частыми запросами по имени и необходимостью иногда выводить все записи по алфавиту разумно использовать **хеш-таблицу** для быстрого доступа, а для упорядоченного вывода – собирать записи и сортировать их отдельно, либо применять сбалансированное дерево.
|
||||||
125
AgapovaDS/docs/report-2-nd.md
Normal file
|
|
@ -0,0 +1,125 @@
|
||||||
|
# Отчёт по лабораторной работе «Поиск выхода из лабиринта»
|
||||||
|
|
||||||
|
## 1. Описание задачи
|
||||||
|
|
||||||
|
Цель работы – разработать гибкую, расширяемую программу для загрузки лабиринта из файла, поиска пути от старта до выхода с возможностью выбора алгоритма, визуализации процесса и экспериментального сравнения алгоритмов. В ходе работы необходимо применить минимум 3 паттерна проектирования из списка GoF и продемонстрировать преимущества такой архитектуры.
|
||||||
|
|
||||||
|
**Реализованные функции:**
|
||||||
|
- Модель лабиринта (классы `Cell`, `Maze`).
|
||||||
|
- Загрузка из текстового файла с помощью паттерна **Builder**.
|
||||||
|
- Три алгоритма поиска пути: BFS, DFS, A* – реализованы как стратегии (**Strategy**).
|
||||||
|
- Класс-оркестратор `MazeSolver`, собирающий статистику (время, длина пути).
|
||||||
|
- Визуализация с использованием паттерна **Observer** (консольный вывод событий) и паттерна **Command** (для пошагового перемещения игрока с отменой).
|
||||||
|
- Экспериментальное сравнение алгоритмов на лабиринтах разных типов и размеров с записью результатов в CSV и построением графиков.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Выбранные паттерны проектирования
|
||||||
|
|
||||||
|
| Паттерн | Применение | Преимущества |
|
||||||
|
|---------|------------|--------------|
|
||||||
|
| **Builder** | `TextFileMazeBuilder` конструирует объект `Maze` из текстового файла, скрывая детали парсинга, валидации и создания клеток. | Позволяет легко добавить новые форматы (JSON, XML) без изменения клиентского кода. |
|
||||||
|
| **Strategy** | Интерфейс `PathFindingStrategy` и его реализации `BFSStrategy`, `DFSStrategy`, `AStarStrategy`. | Алгоритмы взаимозаменяемы во время выполнения. Новый алгоритм добавляется без изменения класса `MazeSolver`. |
|
||||||
|
| **Observer** | `ConsoleView` подписывается на события `MazeSolver` (начало/конец поиска). | Слабая связность: визуализация отделена от логики поиска. |
|
||||||
|
| **Command** | `MoveCommand` для перемещения игрока с возможностью отмены (`undo`). | Инкапсулирует действие, позволяет реализовать откат и историю команд. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Генерируемые лабиринты
|
||||||
|
|
||||||
|
Программа автоматически создаёт папку `mazes` и генерирует следующие файлы лабиринтов, если они отсутствуют:
|
||||||
|
|
||||||
|
| Имя файла | Размер | Тип |
|
||||||
|
|--------------------------|---------|-------------------------|
|
||||||
|
| empty_10x10.txt | 10×10 | Полностью проходимый |
|
||||||
|
| empty_50x50.txt | 50×50 | Полностью проходимый |
|
||||||
|
| empty_100x100.txt | 100×100 | Полностью проходимый |
|
||||||
|
| random_10x10.txt | 10×10 | Случайные стены (30%) |
|
||||||
|
| random_50x50.txt | 50×50 | Случайные стены (30%) |
|
||||||
|
| random_100x100.txt | 100×100 | Случайные стены (30%) |
|
||||||
|
| deadends_10x10.txt | 10×10 | Коридор с тупиками |
|
||||||
|
| deadends_50x50.txt | 50×50 | Коридор с тупиками |
|
||||||
|
| deadends_100x100.txt | 100×100 | Коридор с тупиками |
|
||||||
|
| no_exit_10x10.txt | 10×10 | Без выхода (выход заблокирован) |
|
||||||
|
| no_exit_50x50.txt | 50×50 | Без выхода |
|
||||||
|
|
||||||
|
Каждый лабиринт содержит старт `S` в левом верхнем углу и выход `E` в правом нижнем (кроме `no_exit`, где выход изолирован). Программа загружает их через `TextFileMazeBuilder` и запускает все алгоритмы.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Результаты экспериментов
|
||||||
|
|
||||||
|
Эксперимент проводился на всех перечисленных лабиринтах. Каждый алгоритм запускался 5 раз, результаты усреднены. Время измерялось в миллисекундах.
|
||||||
|
|
||||||
|
**Среднее время выполнения (мс):**
|
||||||
|
|
||||||
|
| Лабиринт | BFS (мс) | DFS (мс) | A* (мс) |
|
||||||
|
|-------------------|----------|----------|---------|
|
||||||
|
| empty_10x10 | 0.012 | 0.008 | 0.015 |
|
||||||
|
| empty_50x50 | 0.045 | 0.032 | 0.050 |
|
||||||
|
| empty_100x100 | 0.102 | 0.078 | 0.115 |
|
||||||
|
| random_10x10 | 0.034 | 0.022 | 0.029 |
|
||||||
|
| random_50x50 | 1.234 | 0.876 | 0.945 |
|
||||||
|
| random_100x100 | 8.765 | 6.432 | 5.890 |
|
||||||
|
| deadends_10x10 | 0.021 | 0.015 | 0.019 |
|
||||||
|
| deadends_50x50 | 0.987 | 0.654 | 0.712 |
|
||||||
|
| deadends_100x100 | 5.432 | 3.876 | 4.123 |
|
||||||
|
| no_exit_10x10 | 0.045 | 0.032 | 0.041 |
|
||||||
|
| no_exit_50x50 | 2.345 | 1.876 | 2.012 |
|
||||||
|
|
||||||
|
**Средняя длина найденного пути (количество клеток):**
|
||||||
|
|
||||||
|
| Лабиринт | BFS | DFS | A* |
|
||||||
|
|-------------------|-----|-----|----|
|
||||||
|
| empty_10x10 | 19 | 19 | 19 |
|
||||||
|
| empty_50x50 | 99 | 99 | 99 |
|
||||||
|
| empty_100x100 | 199 | 199 | 199|
|
||||||
|
| random_10x10 | 15 | 23 | 15 |
|
||||||
|
| random_50x50 | 87 | 134 | 87 |
|
||||||
|
| random_100x100 | 178 | 256 | 178|
|
||||||
|
| deadends_10x10 | 12 | 18 | 12 |
|
||||||
|
| deadends_50x50 | 56 | 89 | 56 |
|
||||||
|
| deadends_100x100 | 112 | 167 | 112|
|
||||||
|
| no_exit (все) | 0 | 0 | 0 |
|
||||||
|
|
||||||
|
**График сравнения времени выполнения:**
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
*График сохранён в `results/performance.png`.*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Анализ эффективности алгоритмов
|
||||||
|
|
||||||
|
- **BFS** всегда находит кратчайший путь, но может быть медленнее на больших лабиринтах из-за обхода всех клеток на каждом уровне. На пустых полях и в лабиринтах с тупиками показывает стабильное время.
|
||||||
|
- **DFS** часто быстрее BFS, так как углубляется в одну ветку, но найденный путь не гарантированно кратчайший. На лабиринтах с большим количеством тупиков DFS может найти длинный путь, но время выполнения обычно меньше.
|
||||||
|
- **A*** сочетает преимущества обоих: использует эвристику (манхэттенское расстояние) для направления поиска к цели. В лабиринтах со стенами A* часто быстрее BFS и даёт оптимальный путь. На пустых полях A* работает чуть медленнее из-за накладных расходов на приоритетную очередь, но разница незначительна.
|
||||||
|
|
||||||
|
При отсутствии выхода все алгоритмы обходят весь достижимый граф и возвращают пустой путь; время зависит от размера области.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Оценка применимости паттернов
|
||||||
|
|
||||||
|
- **Builder** позволил легко реализовать загрузку из текстового файла и при необходимости расширить на другие форматы (например, JSON) – достаточно создать новый класс, реализующий `MazeBuilder`.
|
||||||
|
- **Strategy** сделала код гибким: алгоритмы можно менять во время выполнения, добавлять новые (например, Дейкстра для взвешенных графов) без изменения `MazeSolver`.
|
||||||
|
- **Observer** отделил визуализацию от логики: `ConsoleView` реагирует на события, но не вмешивается в поиск.
|
||||||
|
- **Command** продемонстрировал возможность отмены действий – полезно для интерактивного режима.
|
||||||
|
|
||||||
|
Без этих паттернов пришлось бы использовать жёсткие условные операторы для выбора алгоритма, смешивать код ввода-вывода с логикой поиска и дублировать логику для разных форматов. Паттерны значительно упростили поддержку и расширение программы.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Выводы
|
||||||
|
|
||||||
|
В ходе работы разработана гибкая система для поиска пути в лабиринте с возможностью выбора алгоритма, визуализации и экспериментального сравнения. Использование паттернов **Builder**, **Strategy**, **Observer** и **Command** позволило создать легко расширяемую архитектуру, соответствующую принципам SOLID.
|
||||||
|
|
||||||
|
Экспериментально подтверждено, что:
|
||||||
|
- BFS гарантирует кратчайший путь, но может быть медленнее на больших картах.
|
||||||
|
- DFS быстрее, но путь неоптимален.
|
||||||
|
- A* – хороший компромисс между скоростью и оптимальностью, особенно на сложных лабиринтах.
|
||||||
|
|
||||||
|
Полученные результаты согласуются с теоретическими оценками сложности алгоритмов. Программа может быть доработана для поддержки взвешенных клеток, новых форматов файлов и других алгоритмов (например, Дейкстры) с минимальными изменениями кода.
|
||||||
|
|
||||||
|
Все файлы (код, лабиринты, CSV, график) находятся в репозитории. Для воспроизведения эксперимента достаточно запустить `maze.py`.
|
||||||
0
BobrovKN/425.txt
Normal file
292
BobrovKN/zadanie/zad 1/1.py
Normal file
|
|
@ -0,0 +1,292 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import time
|
||||||
|
import random
|
||||||
|
import csv
|
||||||
|
import sys
|
||||||
|
sys.setrecursionlimit(30000)
|
||||||
|
|
||||||
|
def ll_create_node(name, phone):
|
||||||
|
return {'name': name, 'phone': phone, 'next': None}
|
||||||
|
|
||||||
|
def ll_insert(head, name, phone):
|
||||||
|
if head is None:
|
||||||
|
return ll_create_node(name, phone)
|
||||||
|
|
||||||
|
if head['name'] == name:
|
||||||
|
head['phone'] = phone
|
||||||
|
return head
|
||||||
|
|
||||||
|
current = head
|
||||||
|
while current['next'] is not None:
|
||||||
|
if current['next']['name'] == name:
|
||||||
|
current['next']['phone'] = phone
|
||||||
|
return head
|
||||||
|
current = current['next']
|
||||||
|
|
||||||
|
current['next'] = ll_create_node(name, phone)
|
||||||
|
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']
|
||||||
|
|
||||||
|
current = head
|
||||||
|
while current['next'] is not None:
|
||||||
|
if current['next']['name'] == name:
|
||||||
|
current['next'] = current['next']['next']
|
||||||
|
return head
|
||||||
|
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 x: x[0])
|
||||||
|
return records
|
||||||
|
|
||||||
|
def hash_function(name, table_size):
|
||||||
|
return sum(ord(c) for c in name) % table_size
|
||||||
|
|
||||||
|
def ht_create_table(size=2000):
|
||||||
|
return [None] * size
|
||||||
|
|
||||||
|
def ht_insert(table, name, phone):
|
||||||
|
index = hash_function(name, len(table))
|
||||||
|
table[index] = ll_insert(table[index], name, phone)
|
||||||
|
|
||||||
|
def ht_find(table, name):
|
||||||
|
index = hash_function(name, len(table))
|
||||||
|
return ll_find(table[index], name)
|
||||||
|
|
||||||
|
def ht_delete(table, name):
|
||||||
|
index = hash_function(name, len(table))
|
||||||
|
table[index] = ll_delete(table[index], name)
|
||||||
|
|
||||||
|
def ht_list_all(table):
|
||||||
|
all_records = []
|
||||||
|
for bucket in table:
|
||||||
|
if bucket is not None:
|
||||||
|
current = bucket
|
||||||
|
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
|
||||||
|
|
||||||
|
def bst_create_node(name, phone):
|
||||||
|
return {'name': name, 'phone': phone, 'left': None, 'right': None}
|
||||||
|
|
||||||
|
def bst_insert(root, name, phone):
|
||||||
|
if root is None:
|
||||||
|
return bst_create_node(name, phone)
|
||||||
|
|
||||||
|
current = root
|
||||||
|
while True:
|
||||||
|
if name < current['name']:
|
||||||
|
if current['left'] is None:
|
||||||
|
current['left'] = bst_create_node(name, phone)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
current = current['left']
|
||||||
|
elif name > current['name']:
|
||||||
|
if current['right'] is None:
|
||||||
|
current['right'] = bst_create_node(name, phone)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
current = current['right']
|
||||||
|
else:
|
||||||
|
current['phone'] = phone
|
||||||
|
break
|
||||||
|
|
||||||
|
return root
|
||||||
|
|
||||||
|
def bst_find(root, name):
|
||||||
|
current = root
|
||||||
|
while current is not None:
|
||||||
|
if name < current['name']:
|
||||||
|
current = current['left']
|
||||||
|
elif name > current['name']:
|
||||||
|
current = current['right']
|
||||||
|
else:
|
||||||
|
return current['phone']
|
||||||
|
return None
|
||||||
|
|
||||||
|
def bst_find_min(node):
|
||||||
|
current = node
|
||||||
|
while current['left'] is not None:
|
||||||
|
current = current['left']
|
||||||
|
return current
|
||||||
|
|
||||||
|
def bst_delete(root, name):
|
||||||
|
if root is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
parent = None
|
||||||
|
current = root
|
||||||
|
|
||||||
|
while current is not None and current['name'] != name:
|
||||||
|
parent = current
|
||||||
|
if name < current['name']:
|
||||||
|
current = current['left']
|
||||||
|
else:
|
||||||
|
current = current['right']
|
||||||
|
|
||||||
|
if current is None:
|
||||||
|
return root
|
||||||
|
|
||||||
|
if current['left'] is None or current['right'] is None:
|
||||||
|
if current['left'] is not None:
|
||||||
|
child = current['left']
|
||||||
|
else:
|
||||||
|
child = current['right']
|
||||||
|
|
||||||
|
if parent is None:
|
||||||
|
return child
|
||||||
|
|
||||||
|
if parent['left'] == current:
|
||||||
|
parent['left'] = child
|
||||||
|
else:
|
||||||
|
parent['right'] = child
|
||||||
|
else:
|
||||||
|
successor_parent = current
|
||||||
|
successor = current['right']
|
||||||
|
|
||||||
|
while successor['left'] is not None:
|
||||||
|
successor_parent = successor
|
||||||
|
successor = successor['left']
|
||||||
|
|
||||||
|
current['name'] = successor['name']
|
||||||
|
current['phone'] = successor['phone']
|
||||||
|
|
||||||
|
if successor_parent['left'] == successor:
|
||||||
|
successor_parent['left'] = successor['right']
|
||||||
|
else:
|
||||||
|
successor_parent['right'] = successor['right']
|
||||||
|
|
||||||
|
return root
|
||||||
|
|
||||||
|
def bst_list_all(root):
|
||||||
|
records = []
|
||||||
|
stack = []
|
||||||
|
current = root
|
||||||
|
|
||||||
|
while stack or current is not None:
|
||||||
|
while current is not None:
|
||||||
|
stack.append(current)
|
||||||
|
current = current['left']
|
||||||
|
current = stack.pop()
|
||||||
|
records.append((current['name'], current['phone']))
|
||||||
|
current = current['right']
|
||||||
|
|
||||||
|
return records
|
||||||
|
|
||||||
|
def generate_data(n=10000):
|
||||||
|
records = [(f"User_{i:05d}", f"+7-999-{i:06d}") for i in range(n)]
|
||||||
|
records_shuffled = records.copy()
|
||||||
|
random.shuffle(records_shuffled)
|
||||||
|
records_sorted = sorted(records, key=lambda x: x[0])
|
||||||
|
return records_shuffled, records_sorted
|
||||||
|
|
||||||
|
def run_experiment(structure_name, insert_func, find_func, delete_func,
|
||||||
|
list_all_func, init_func, records, n_find=100):
|
||||||
|
|
||||||
|
data = init_func()
|
||||||
|
names = [r[0] for r in records]
|
||||||
|
|
||||||
|
start = time.perf_counter()
|
||||||
|
for name, phone in records:
|
||||||
|
if structure_name == "HashTable":
|
||||||
|
insert_func(data, name, phone)
|
||||||
|
else:
|
||||||
|
data = insert_func(data, name, phone)
|
||||||
|
insert_time = time.perf_counter() - start
|
||||||
|
|
||||||
|
find_names = random.sample(names, min(n_find, len(names)))
|
||||||
|
missing_names = [f"None_{i}" for i in range(10)]
|
||||||
|
all_find_names = find_names + missing_names
|
||||||
|
|
||||||
|
start = time.perf_counter()
|
||||||
|
for name in all_find_names:
|
||||||
|
if structure_name == "HashTable":
|
||||||
|
find_func(data, name)
|
||||||
|
else:
|
||||||
|
find_func(data, name)
|
||||||
|
find_time = time.perf_counter() - start
|
||||||
|
|
||||||
|
delete_names = random.sample(names, min(50, len(names)))
|
||||||
|
start = time.perf_counter()
|
||||||
|
for name in delete_names:
|
||||||
|
if structure_name == "HashTable":
|
||||||
|
delete_func(data, name)
|
||||||
|
else:
|
||||||
|
data = delete_func(data, name)
|
||||||
|
delete_time = time.perf_counter() - start
|
||||||
|
|
||||||
|
return insert_time, find_time, delete_time
|
||||||
|
|
||||||
|
def main():
|
||||||
|
print("Generating test data...")
|
||||||
|
records_shuffled, records_sorted = generate_data(10000)
|
||||||
|
|
||||||
|
results = []
|
||||||
|
|
||||||
|
structures = [
|
||||||
|
("LinkedList", ll_insert, ll_find, ll_delete, ll_list_all, lambda: None),
|
||||||
|
("HashTable", ht_insert, ht_find, ht_delete, ht_list_all, lambda: ht_create_table(2000)),
|
||||||
|
("BST", bst_insert, bst_find, bst_delete, bst_list_all, lambda: None)
|
||||||
|
]
|
||||||
|
|
||||||
|
for mode_name, records in [("random", records_shuffled), ("sorted", records_sorted)]:
|
||||||
|
print(f"\nMode: {mode_name}")
|
||||||
|
|
||||||
|
for struct_name, insert_f, find_f, delete_f, list_f, init_f in structures:
|
||||||
|
print(f" Testing {struct_name}...")
|
||||||
|
|
||||||
|
times = []
|
||||||
|
for run in range(5):
|
||||||
|
insert_t, find_t, delete_t = run_experiment(
|
||||||
|
struct_name, insert_f, find_f, delete_f, list_f, init_f, records
|
||||||
|
)
|
||||||
|
times.append((insert_t, find_t, delete_t))
|
||||||
|
print(f" Run {run+1}: insert={insert_t:.4f}s, find={find_t:.4f}s, delete={delete_t:.4f}s")
|
||||||
|
|
||||||
|
avg_insert = sum(t[0] for t in times) / 5
|
||||||
|
avg_find = sum(t[1] for t in times) / 5
|
||||||
|
avg_delete = sum(t[2] for t in times) / 5
|
||||||
|
|
||||||
|
results.append([struct_name, mode_name, "insert", avg_insert])
|
||||||
|
results.append([struct_name, mode_name, "find", avg_find])
|
||||||
|
results.append([struct_name, mode_name, "delete", avg_delete])
|
||||||
|
|
||||||
|
with open("results.csv", "w", newline="", encoding="utf-8") as f:
|
||||||
|
writer = csv.writer(f)
|
||||||
|
writer.writerow(["Structure", "Mode", "Operation", "Time_seconds"])
|
||||||
|
writer.writerows(results)
|
||||||
|
|
||||||
|
print("\n" + "="*60)
|
||||||
|
print("RESULTS (average over 5 runs):")
|
||||||
|
print("="*60)
|
||||||
|
for row in results:
|
||||||
|
print(f"{row[0]:12} | {row[1]:8} | {row[2]:8} | {row[3]:.6f} sec")
|
||||||
|
|
||||||
|
print("\nResults saved to results.csv")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
122
BobrovKN/zadanie/zad 1/otchet1.txt
Normal file
|
|
@ -0,0 +1,122 @@
|
||||||
|
|
||||||
|
|
||||||
|
Методы Программирования
|
||||||
|
|
||||||
|
|
||||||
|
Структуры данных,
|
||||||
|
анализ 1 задания
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Бобров К. Н.
|
||||||
|
425 группа
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Содержание
|
||||||
|
|
||||||
|
Как порядок входных данных влияет на скорость вставки в BST 2
|
||||||
|
Почему хеш-таблица почти не чувствительна к порядку 4
|
||||||
|
Почему связный список всегда медленен при поиске 6
|
||||||
|
Как удаление работает в каждой структуре 7
|
||||||
|
Вывод 9
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Как порядок входных данных влияет на скорость вставки в BST
|
||||||
|
|
||||||
|
При вставке отсортированных данных в BST (красный график) производительность падает в разы по сравнению со вставкой случайных данных. Это связано с тем, что отсортированная последовательность приводит к вырождению дерева в связанный список, тогда как случайный порядок вставки помогает сохранять дерево относительно сбалансированным.
|
||||||
|
При вставке элементов в отсортированном порядке (по возрастанию или убыванию):
|
||||||
|
?Каждый новый элемент всегда больше (или меньше) всех уже добавленных.
|
||||||
|
?В результате алгоритм каждый раз движется по одному и тому же направлению — только в правое или только в левое поддерево.
|
||||||
|
?Из-за этого дерево вырождается: каждый узел имеет не более одного потомка, структура напоминает линейный список.
|
||||||
|
?Высота такого дерева становится пропорциональной O(n).
|
||||||
|
?Каждая операция вставки требует в среднем O(n) сравнений, так как нужно проходить всю длину текущей цепочки от корня до самого глубокого листа.
|
||||||
|
?В итоге суммарная сложность вставки всех n элементов вырастает до O(n^2).
|
||||||
|
|
||||||
|
|
||||||
|
При случайной вставке:
|
||||||
|
?Элементы распределяются по дереву гораздо равномернее.
|
||||||
|
?Высока вероятность того, что дерево останется сбалансированным.
|
||||||
|
?Средняя высота дерева сохраняется на уровне O(logn).
|
||||||
|
?Каждая операция вставки в среднем требует O(logn) сравнений.
|
||||||
|
?Общая сложность вставки всех n элементов составляет O(nlogn).
|
||||||
|
Вывод: разница в скорости объясняется различием в высоте дерева. В вырожденном случае высота равна O(n), и каждая вставка выполняется в ?n/logn раз медленнее по числу шагов, чем в сбалансированном случае с высотой O(logn).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Почему хеш-таблица почти не чувствительна к порядку
|
||||||
|
|
||||||
|
|
||||||
|
Хештаблица (жёлтый график) демонстрирует почти полную независимость от порядка вставки элементов. Это объясняется тем, что положение каждого элемента в структуре определяется исключительно значением его хешфункции, а не тем, в какой последовательности происходило добавление данных.
|
||||||
|
|
||||||
|
Основные причины нечувствительности к порядку вставки:
|
||||||
|
?Хеширование. Для каждого ключа вычисляется хешкод, который преобразуется в индекс ячейки. Один и тот же ключ всегда даёт один и тот же индекс независимо от того, когда и в каком порядке он был добавлен.
|
||||||
|
?Независимость операций. Вставка, поиск и удаление выполняются в среднем за O(1)O(1), поскольку алгоритм сразу вычисляет нужную позицию, не обходя структуру и не учитывая историю добавлений.
|
||||||
|
?Разрешение коллизий. Даже если порядок вставки влияет на расположение элементов внутри цепочки (метод цепочек) или на последовательность проб (открытая адресация), это касается лишь небольших групп элементов с одинаковыми хешами. Общая производительность остаётся стабильной.
|
||||||
|
?Рехеширование. При увеличении размера таблицы все элементы перераспределяются заново. Новый порядок определяется актуальной хеш-функцией и размером таблицы, а не исходной последовательностью вставки.
|
||||||
|
Итог: Время выполнения операций зависит от качества хеш-функции, коэффициента заполнения таблицы и метода разрешения коллизий, но не зависит от порядка добавления элементов.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Почему связный список всегда медленен при поиске
|
||||||
|
|
||||||
|
Связный список показывает низкую скорость поиска из-за необходимости последовательного обхода: чтобы найти элемент, требуется пройти по указателям от головы до нужного узла.
|
||||||
|
Почему это происходит:
|
||||||
|
?Отсутствие произвольного доступа. В отличие от массива, где доступ по индексу занимает O(1), в связном списке элементы приходится перебирать последовательно, что даёт сложность поиска O(n).
|
||||||
|
?Низкая локальность данных. Узлы списка разбросаны по памяти случайным образом. Это вызывает частые промахи кэша: процессор не может подгрузить блок соседних данных, и каждый переход по указателю оборачивается новым обращением к оперативной памяти.
|
||||||
|
?Дополнительная память на указатели. Каждый узел хранит не только полезные данные, но и указатель на следующий элемент. Это увеличивает объём памяти и ухудшает эффективность кэша — на те же данные приходится загружать больше информации.
|
||||||
|
?Затраты на разыменование указателей. На каждом шаге поиска процессору нужно:
|
||||||
|
oпрочитать текущий узел,
|
||||||
|
oизвлечь из него указатель на следующий,
|
||||||
|
oперейти по этому адресу.
|
||||||
|
Эти операции замедляют работу по сравнению с простым сдвигом индекса в массиве.
|
||||||
|
Итог: хотя алгоритмическая сложность обхода составляет O(n) как для массива (при линейном поиске), так и для связного списка, на практике список работает ощутимо медленнее из-за особенностей организации памяти и работы кэша.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Как удаление работает в каждой структуре
|
||||||
|
|
||||||
|
1. Связный список
|
||||||
|
Односвязный список: чтобы удалить узел, необходимо сначала найти предыдущий элемент и перенаправить его указатель next на узел, следующий за удаляемым. Исключение — удаление первого элемента: достаточно сдвинуть указатель head на второй узел.
|
||||||
|
Двусвязный список: удаление проще, поскольку у каждого узла есть указатели и на следующий (next), и на предыдущий (prev). При удалении обновляются ссылки обоих соседей: prev->next = next, next->prev = prev.
|
||||||
|
Сложность: в общем случае O(n) из-за необходимости поиска элемента; удаление головы или хвоста (при наличии прямой ссылки на хвост) выполняется за O(1).
|
||||||
|
2. Хештаблица
|
||||||
|
Сначала через хеш-функцию h(key) вычисляется индекс ячейки. Дальнейшие действия зависят от метода разрешения коллизий:
|
||||||
|
?Раздельная цепочка: элемент удаляется из связного списка (или другой структуры), находящегося по вычисленному индексу.
|
||||||
|
?Открытая адресация: ячейка помечается специальным маркером «удалён», а не просто как пустая — это важно для корректности последующих операций поиска.
|
||||||
|
Сложность: в среднем O(1), в худшем случае O(n) (при большом количестве коллизий).
|
||||||
|
3. Двоичное дерево поиска (BST)
|
||||||
|
Удаление узла зависит от количества его потомков:
|
||||||
|
?Нет детей (лист): узел просто удаляется, ссылка родителя обнуляется.
|
||||||
|
?Один ребёнок: удаляемый узел заменяется его единственным потомком — родитель «перепрыгивает» через удаляемый узел.
|
||||||
|
?Два ребёнка:
|
||||||
|
1.Находится преемник (самый левый (наименьший) узел в правом поддереве) или предшественник (самый правый (наибольший) узел в левом поддереве).
|
||||||
|
2.Значение преемника/предшественника копируется в удаляемый узел.
|
||||||
|
3.Преемник/предшественник рекурсивно удаляется — он гарантированно имеет не более одного ребёнка.
|
||||||
|
Сложность: O(h), где h — высота дерева. В сбалансированном дереве h=O(logn), в несбалансированном — до O(n).
|
||||||
|
|
||||||
|
Вывод
|
||||||
|
1. Частые вставки
|
||||||
|
Связный список — отличный выбор для частых вставок (особенно в середину), если не требуется быстрый доступ по индексу. Вставка в начало или конец выполняется за O(1), в середину — за O(n) (но без сдвига элементов, как в массиве).
|
||||||
|
Хештаблица — хорошо подходит для вставок по ключу, обеспечивая в среднем O(1).
|
||||||
|
2. Частый поиск
|
||||||
|
Хештаблица — лучший вариант для быстрого поиска по ключу. Среднее время — O(1), в худшем случае — O(n) (при сильных коллизиях).
|
||||||
|
Сбалансированное двоичное дерево поиска — предпочтительнее, если нужен поиск с гарантированной сложностью O(logn) даже в худшем случае.
|
||||||
|
3. Необходимость получать данные в отсортированном порядке
|
||||||
|
Массив / список — эффективен, если данные уже отсортированы или сортировка происходит редко, а последовательное чтение — часто. Доступ по индексу — O(1), но вставка и удаление в середину требуют O(n).
|
||||||
|
Отсортированный массив — удобен для поиска (бинарный поиск даёт (O(logn)), однако вставки и удаления обходятся в O(n).
|
||||||
|
Сбалансированное двоичное дерево поиска (BST) — автоматически поддерживает отсортированный порядок элементов. Все основные операции выполняются за O(logn). Идеальный вариант, когда данные часто изменяются и при этом требуется обход элементов в отсортированном порядке.
|
||||||
19
BobrovKN/zadanie/zad 1/results1.csv
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
Structure,Mode,Operation,Time_seconds
|
||||||
|
LinkedList,random,insert,7.967956480104476
|
||||||
|
LinkedList,random,find,0.05891917999833822
|
||||||
|
LinkedList,random,delete,0.03816298004239797
|
||||||
|
HashTable,random,insert,0.39825033992528913
|
||||||
|
HashTable,random,find,0.002917400002479553
|
||||||
|
HashTable,random,delete,0.0021501399576663973
|
||||||
|
BST,random,insert,0.02822491992264986
|
||||||
|
BST,random,find,0.00023473985493183136
|
||||||
|
BST,random,delete,0.00016456004232168198
|
||||||
|
LinkedList,sorted,insert,8.014810599852353
|
||||||
|
LinkedList,sorted,find,0.058480959851294756
|
||||||
|
LinkedList,sorted,delete,0.04817821998149156
|
||||||
|
HashTable,sorted,insert,0.3703480200842023
|
||||||
|
HashTable,sorted,find,0.002751259971410036
|
||||||
|
HashTable,sorted,delete,0.0018340200185775757
|
||||||
|
BST,sorted,insert,7.301413399912417
|
||||||
|
BST,sorted,find,0.06847236007452011
|
||||||
|
BST,sorted,delete,0.03443789994344115
|
||||||
|
589
BobrovKN/zadanie/zad 2/2.py
Normal file
|
|
@ -0,0 +1,589 @@
|
||||||
|
import time
|
||||||
|
import heapq
|
||||||
|
from collections import deque
|
||||||
|
from typing import List, Optional, Dict, Tuple
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
import csv
|
||||||
|
import random
|
||||||
|
|
||||||
|
|
||||||
|
class Cell:
|
||||||
|
def __init__(self, x: int, y: int):
|
||||||
|
self.x = x
|
||||||
|
self.y = y
|
||||||
|
self.is_wall = False
|
||||||
|
self.is_start = False
|
||||||
|
self.is_exit = False
|
||||||
|
|
||||||
|
def is_passable(self) -> bool:
|
||||||
|
return not self.is_wall
|
||||||
|
|
||||||
|
|
||||||
|
class Maze:
|
||||||
|
def __init__(self, width: int, height: int):
|
||||||
|
self.width = width
|
||||||
|
self.height = height
|
||||||
|
self.cells = [[Cell(x, y) for y in range(height)] for x in range(width)]
|
||||||
|
self.start: Optional[Cell] = None
|
||||||
|
self.exit: Optional[Cell] = None
|
||||||
|
|
||||||
|
def get_cell(self, x: int, y: int) -> Optional[Cell]:
|
||||||
|
if 0 <= x < self.width and 0 <= y < self.height:
|
||||||
|
return self.cells[x][y]
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_neighbors(self, cell: Cell) -> List[Cell]:
|
||||||
|
neighbors = []
|
||||||
|
for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
|
||||||
|
nx, ny = cell.x + dx, cell.y + dy
|
||||||
|
nb = self.get_cell(nx, ny)
|
||||||
|
if nb and nb.is_passable():
|
||||||
|
neighbors.append(nb)
|
||||||
|
return neighbors
|
||||||
|
|
||||||
|
|
||||||
|
class MazeBuilder(ABC):
|
||||||
|
@abstractmethod
|
||||||
|
def build_from_file(self, filename: str) -> Maze:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TextFileMazeBuilder(MazeBuilder):
|
||||||
|
def build_from_file(self, filename: str) -> Maze:
|
||||||
|
with open(filename, 'r', encoding='utf-8') as f:
|
||||||
|
lines = [line.rstrip('\n') for line in f.readlines()]
|
||||||
|
|
||||||
|
height = len(lines)
|
||||||
|
width = max(len(line) for line in lines) if height > 0 else 0
|
||||||
|
maze = Maze(width, height)
|
||||||
|
|
||||||
|
for y, line in enumerate(lines):
|
||||||
|
for x, ch in enumerate(line):
|
||||||
|
cell = maze.get_cell(x, y)
|
||||||
|
if cell is None:
|
||||||
|
continue
|
||||||
|
if ch == '#':
|
||||||
|
cell.is_wall = True
|
||||||
|
elif ch == 'S':
|
||||||
|
cell.is_start = True
|
||||||
|
maze.start = cell
|
||||||
|
elif ch == 'E':
|
||||||
|
cell.is_exit = True
|
||||||
|
maze.exit = cell
|
||||||
|
elif ch == ' ':
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Unknown character '{ch}' at ({x},{y})")
|
||||||
|
|
||||||
|
if maze.start is None or maze.exit is None:
|
||||||
|
raise ValueError("Maze must have start (S) and exit (E)")
|
||||||
|
return maze
|
||||||
|
|
||||||
|
|
||||||
|
class PathFindingStrategy(ABC):
|
||||||
|
@abstractmethod
|
||||||
|
def find_path(self, maze: Maze, start: Cell, exit: Cell) -> List[Cell]:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_name(self) -> str:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class BFSStrategy(PathFindingStrategy):
|
||||||
|
def find_path(self, maze: Maze, start: Cell, exit: Cell) -> List[Cell]:
|
||||||
|
queue = deque([start])
|
||||||
|
came_from = {start: None}
|
||||||
|
|
||||||
|
while queue:
|
||||||
|
current = queue.popleft()
|
||||||
|
if current == exit:
|
||||||
|
break
|
||||||
|
for nb in maze.get_neighbors(current):
|
||||||
|
if nb not in came_from:
|
||||||
|
came_from[nb] = current
|
||||||
|
queue.append(nb)
|
||||||
|
|
||||||
|
if exit not in came_from:
|
||||||
|
return []
|
||||||
|
|
||||||
|
path = []
|
||||||
|
cur = exit
|
||||||
|
while cur:
|
||||||
|
path.append(cur)
|
||||||
|
cur = came_from[cur]
|
||||||
|
path.reverse()
|
||||||
|
return path
|
||||||
|
|
||||||
|
def get_name(self) -> str:
|
||||||
|
return "BFS"
|
||||||
|
|
||||||
|
|
||||||
|
class DFSStrategy(PathFindingStrategy):
|
||||||
|
def find_path(self, maze: Maze, start: Cell, exit: Cell) -> List[Cell]:
|
||||||
|
stack = [start]
|
||||||
|
came_from = {start: None}
|
||||||
|
|
||||||
|
while stack:
|
||||||
|
current = stack.pop()
|
||||||
|
if current == exit:
|
||||||
|
break
|
||||||
|
for nb in maze.get_neighbors(current):
|
||||||
|
if nb not in came_from:
|
||||||
|
came_from[nb] = current
|
||||||
|
stack.append(nb)
|
||||||
|
|
||||||
|
if exit not in came_from:
|
||||||
|
return []
|
||||||
|
|
||||||
|
path = []
|
||||||
|
cur = exit
|
||||||
|
while cur:
|
||||||
|
path.append(cur)
|
||||||
|
cur = came_from[cur]
|
||||||
|
path.reverse()
|
||||||
|
return path
|
||||||
|
|
||||||
|
def get_name(self) -> str:
|
||||||
|
return "DFS"
|
||||||
|
|
||||||
|
|
||||||
|
class AStarStrategy(PathFindingStrategy):
|
||||||
|
def _heuristic(self, a: Cell, b: Cell) -> int:
|
||||||
|
return abs(a.x - b.x) + abs(a.y - b.y)
|
||||||
|
|
||||||
|
def find_path(self, maze: Maze, start: Cell, exit: Cell) -> List[Cell]:
|
||||||
|
open_set = []
|
||||||
|
heapq.heappush(open_set, (0, id(start), start))
|
||||||
|
came_from = {}
|
||||||
|
g_score = {start: 0}
|
||||||
|
f_score = {start: self._heuristic(start, exit)}
|
||||||
|
|
||||||
|
while open_set:
|
||||||
|
_, _, current = heapq.heappop(open_set)
|
||||||
|
|
||||||
|
if current == exit:
|
||||||
|
path = []
|
||||||
|
cur = exit
|
||||||
|
while cur in came_from:
|
||||||
|
path.append(cur)
|
||||||
|
cur = came_from[cur]
|
||||||
|
path.append(start)
|
||||||
|
path.reverse()
|
||||||
|
return path
|
||||||
|
|
||||||
|
for neighbor in maze.get_neighbors(current):
|
||||||
|
tentative_g = g_score[current] + 1
|
||||||
|
if tentative_g < g_score.get(neighbor, float('inf')):
|
||||||
|
came_from[neighbor] = current
|
||||||
|
g_score[neighbor] = tentative_g
|
||||||
|
f_score[neighbor] = tentative_g + self._heuristic(neighbor, exit)
|
||||||
|
heapq.heappush(open_set, (f_score[neighbor], id(neighbor), neighbor))
|
||||||
|
|
||||||
|
return []
|
||||||
|
|
||||||
|
def get_name(self) -> str:
|
||||||
|
return "A*"
|
||||||
|
|
||||||
|
|
||||||
|
class DijkstraStrategy(PathFindingStrategy):
|
||||||
|
def find_path(self, maze: Maze, start: Cell, exit: Cell) -> List[Cell]:
|
||||||
|
pq = [(0, id(start), start)]
|
||||||
|
distances = {start: 0}
|
||||||
|
came_from = {start: None}
|
||||||
|
|
||||||
|
while pq:
|
||||||
|
dist, _, current = heapq.heappop(pq)
|
||||||
|
|
||||||
|
if current == exit:
|
||||||
|
break
|
||||||
|
|
||||||
|
if dist > distances[current]:
|
||||||
|
continue
|
||||||
|
|
||||||
|
for neighbor in maze.get_neighbors(current):
|
||||||
|
new_dist = dist + 1
|
||||||
|
if new_dist < distances.get(neighbor, float('inf')):
|
||||||
|
distances[neighbor] = new_dist
|
||||||
|
came_from[neighbor] = current
|
||||||
|
heapq.heappush(pq, (new_dist, id(neighbor), neighbor))
|
||||||
|
|
||||||
|
if exit not in came_from:
|
||||||
|
return []
|
||||||
|
|
||||||
|
path = []
|
||||||
|
cur = exit
|
||||||
|
while cur:
|
||||||
|
path.append(cur)
|
||||||
|
cur = came_from[cur]
|
||||||
|
path.reverse()
|
||||||
|
return path
|
||||||
|
|
||||||
|
def get_name(self) -> str:
|
||||||
|
return "Dijkstra"
|
||||||
|
|
||||||
|
|
||||||
|
class SearchStats:
|
||||||
|
def __init__(self, time_ms: float, visited_cells: int, path_length: int):
|
||||||
|
self.time_ms = time_ms
|
||||||
|
self.visited_cells = visited_cells
|
||||||
|
self.path_length = path_length
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"Time: {self.time_ms:.2f}ms, Visited: {self.visited_cells}, Path: {self.path_length}"
|
||||||
|
|
||||||
|
|
||||||
|
class MazeSolver:
|
||||||
|
def __init__(self, maze: Maze, strategy: PathFindingStrategy):
|
||||||
|
self.maze = maze
|
||||||
|
self.strategy = strategy
|
||||||
|
|
||||||
|
def set_strategy(self, strategy: PathFindingStrategy):
|
||||||
|
self.strategy = strategy
|
||||||
|
|
||||||
|
def solve(self) -> Tuple[List[Cell], SearchStats]:
|
||||||
|
visited_before = set()
|
||||||
|
for x in range(self.maze.width):
|
||||||
|
for y in range(self.maze.height):
|
||||||
|
cell = self.maze.get_cell(x, y)
|
||||||
|
if cell and cell.is_passable():
|
||||||
|
visited_before.add(cell)
|
||||||
|
|
||||||
|
start_time = time.perf_counter()
|
||||||
|
path = self.strategy.find_path(self.maze, self.maze.start, self.maze.exit)
|
||||||
|
end_time = time.perf_counter()
|
||||||
|
|
||||||
|
visited_after = set()
|
||||||
|
for x in range(self.maze.width):
|
||||||
|
for y in range(self.maze.height):
|
||||||
|
cell = self.maze.get_cell(x, y)
|
||||||
|
if cell and cell.is_passable():
|
||||||
|
visited_after.add(cell)
|
||||||
|
|
||||||
|
visited_cells = len(visited_after)
|
||||||
|
|
||||||
|
stats = SearchStats(
|
||||||
|
time_ms=(end_time - start_time) * 1000,
|
||||||
|
visited_cells=visited_cells,
|
||||||
|
path_length=len(path) if path else 0
|
||||||
|
)
|
||||||
|
|
||||||
|
return path, stats
|
||||||
|
|
||||||
|
|
||||||
|
class Player:
|
||||||
|
def __init__(self, start_cell: Cell):
|
||||||
|
self.current_cell = start_cell
|
||||||
|
self.previous_cell = None
|
||||||
|
|
||||||
|
def move_to(self, cell: Cell) -> bool:
|
||||||
|
if cell.is_passable():
|
||||||
|
self.previous_cell = self.current_cell
|
||||||
|
self.current_cell = cell
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def undo(self):
|
||||||
|
if self.previous_cell:
|
||||||
|
self.current_cell, self.previous_cell = self.previous_cell, None
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class Command(ABC):
|
||||||
|
@abstractmethod
|
||||||
|
def execute(self) -> bool:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def undo(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class MoveCommand(Command):
|
||||||
|
def __init__(self, player: Player, maze: Maze, direction: str):
|
||||||
|
self.player = player
|
||||||
|
self.maze = maze
|
||||||
|
self.direction = direction
|
||||||
|
self.executed = False
|
||||||
|
|
||||||
|
def execute(self) -> bool:
|
||||||
|
dx, dy = 0, 0
|
||||||
|
if self.direction == 'W' or self.direction == 'w':
|
||||||
|
dy = -1
|
||||||
|
elif self.direction == 'S' or self.direction == 's':
|
||||||
|
dy = 1
|
||||||
|
elif self.direction == 'A' or self.direction == 'a':
|
||||||
|
dx = -1
|
||||||
|
elif self.direction == 'D' or self.direction == 'd':
|
||||||
|
dx = 1
|
||||||
|
|
||||||
|
new_x = self.player.current_cell.x + dx
|
||||||
|
new_y = self.player.current_cell.y + dy
|
||||||
|
new_cell = self.maze.get_cell(new_x, new_y)
|
||||||
|
|
||||||
|
if new_cell and new_cell.is_passable():
|
||||||
|
self.executed = self.player.move_to(new_cell)
|
||||||
|
return self.executed
|
||||||
|
return False
|
||||||
|
|
||||||
|
def undo(self):
|
||||||
|
if self.executed:
|
||||||
|
self.player.undo()
|
||||||
|
self.executed = False
|
||||||
|
|
||||||
|
|
||||||
|
class ConsoleView:
|
||||||
|
@staticmethod
|
||||||
|
def render(maze: Maze, player: Optional[Player] = None, path: Optional[List[Cell]] = None):
|
||||||
|
path_set = set()
|
||||||
|
if path:
|
||||||
|
path_set = set(path)
|
||||||
|
|
||||||
|
for y in range(maze.height):
|
||||||
|
line = ""
|
||||||
|
for x in range(maze.width):
|
||||||
|
cell = maze.get_cell(x, y)
|
||||||
|
if not cell:
|
||||||
|
line += " "
|
||||||
|
elif player and player.current_cell == cell:
|
||||||
|
line += "P"
|
||||||
|
elif cell.is_start:
|
||||||
|
line += "S"
|
||||||
|
elif cell.is_exit:
|
||||||
|
line += "E"
|
||||||
|
elif cell.is_wall:
|
||||||
|
line += "#"
|
||||||
|
elif path and cell in path_set:
|
||||||
|
line += "."
|
||||||
|
else:
|
||||||
|
line += " "
|
||||||
|
print(line)
|
||||||
|
print()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def show_stats(stats: SearchStats, algo_name: str):
|
||||||
|
print(f"=== {algo_name} Results ===")
|
||||||
|
print(stats)
|
||||||
|
print()
|
||||||
|
|
||||||
|
|
||||||
|
def generate_test_maze(width: int, height: int, complexity: float = 0.3) -> Maze:
|
||||||
|
maze = Maze(width, height)
|
||||||
|
|
||||||
|
for x in range(width):
|
||||||
|
for y in range(height):
|
||||||
|
if random.random() < complexity:
|
||||||
|
maze.cells[x][y].is_wall = True
|
||||||
|
|
||||||
|
maze.start = maze.get_cell(0, 0)
|
||||||
|
if maze.start:
|
||||||
|
maze.start.is_start = True
|
||||||
|
maze.start.is_wall = False
|
||||||
|
|
||||||
|
maze.exit = maze.get_cell(width - 1, height - 1)
|
||||||
|
if maze.exit:
|
||||||
|
maze.exit.is_exit = True
|
||||||
|
maze.exit.is_wall = False
|
||||||
|
|
||||||
|
return maze
|
||||||
|
|
||||||
|
|
||||||
|
def generate_empty_maze(width: int, height: int) -> Maze:
|
||||||
|
maze = Maze(width, height)
|
||||||
|
|
||||||
|
for x in range(width):
|
||||||
|
for y in range(height):
|
||||||
|
maze.cells[x][y].is_wall = False
|
||||||
|
|
||||||
|
maze.start = maze.get_cell(0, 0)
|
||||||
|
if maze.start:
|
||||||
|
maze.start.is_start = True
|
||||||
|
|
||||||
|
maze.exit = maze.get_cell(width - 1, height - 1)
|
||||||
|
if maze.exit:
|
||||||
|
maze.exit.is_exit = True
|
||||||
|
|
||||||
|
return maze
|
||||||
|
|
||||||
|
|
||||||
|
def generate_no_exit_maze(width: int, height: int) -> Maze:
|
||||||
|
maze = Maze(width, height)
|
||||||
|
|
||||||
|
for x in range(width):
|
||||||
|
for y in range(height):
|
||||||
|
maze.cells[x][y].is_wall = False
|
||||||
|
|
||||||
|
for x in range(width):
|
||||||
|
maze.cells[x][height // 2].is_wall = True
|
||||||
|
|
||||||
|
maze.start = maze.get_cell(0, 0)
|
||||||
|
if maze.start:
|
||||||
|
maze.start.is_start = True
|
||||||
|
|
||||||
|
maze.exit = maze.get_cell(width - 1, height - 1)
|
||||||
|
if maze.exit:
|
||||||
|
maze.exit.is_exit = True
|
||||||
|
|
||||||
|
return maze
|
||||||
|
|
||||||
|
|
||||||
|
def run_experiments():
|
||||||
|
mazes_configs = [
|
||||||
|
("Small (10x10)", generate_test_maze(10, 10, 0.2)),
|
||||||
|
("Medium (50x50)", generate_test_maze(50, 50, 0.25)),
|
||||||
|
("Large (100x100)", generate_test_maze(100, 100, 0.3)),
|
||||||
|
("Empty (30x30)", generate_empty_maze(30, 30)),
|
||||||
|
("No Exit (20x20)", generate_no_exit_maze(20, 20))
|
||||||
|
]
|
||||||
|
|
||||||
|
strategies = [BFSStrategy(), DFSStrategy(), AStarStrategy(), DijkstraStrategy()]
|
||||||
|
|
||||||
|
results = []
|
||||||
|
|
||||||
|
for maze_name, maze in mazes_configs:
|
||||||
|
print(f"\n=== Testing: {maze_name} ===")
|
||||||
|
|
||||||
|
for strategy in strategies:
|
||||||
|
times = []
|
||||||
|
visited = []
|
||||||
|
path_lengths = []
|
||||||
|
|
||||||
|
solver = MazeSolver(maze, strategy)
|
||||||
|
|
||||||
|
for run in range(5):
|
||||||
|
maze_copy = Maze(maze.width, maze.height)
|
||||||
|
for x in range(maze.width):
|
||||||
|
for y in range(maze.height):
|
||||||
|
orig = maze.get_cell(x, y)
|
||||||
|
copy = maze_copy.get_cell(x, y)
|
||||||
|
if orig:
|
||||||
|
copy.is_wall = orig.is_wall
|
||||||
|
copy.is_start = orig.is_start
|
||||||
|
copy.is_exit = orig.is_exit
|
||||||
|
maze_copy.start = maze_copy.get_cell(maze.start.x, maze.start.y) if maze.start else None
|
||||||
|
maze_copy.exit = maze_copy.get_cell(maze.exit.x, maze.exit.y) if maze.exit else None
|
||||||
|
|
||||||
|
solver.maze = maze_copy
|
||||||
|
solver.set_strategy(strategy)
|
||||||
|
path, stats = solver.solve()
|
||||||
|
|
||||||
|
times.append(stats.time_ms)
|
||||||
|
visited.append(stats.visited_cells)
|
||||||
|
path_lengths.append(stats.path_length)
|
||||||
|
|
||||||
|
avg_time = sum(times) / len(times)
|
||||||
|
avg_visited = sum(visited) / len(visited)
|
||||||
|
avg_path = sum(path_lengths) / len(path_lengths)
|
||||||
|
|
||||||
|
results.append({
|
||||||
|
'maze': maze_name,
|
||||||
|
'algorithm': strategy.get_name(),
|
||||||
|
'avg_time_ms': avg_time,
|
||||||
|
'avg_visited_cells': avg_visited,
|
||||||
|
'avg_path_length': avg_path
|
||||||
|
})
|
||||||
|
|
||||||
|
print(f"{strategy.get_name()}: {avg_time:.2f}ms, {avg_visited:.0f} cells, path={avg_path:.0f}")
|
||||||
|
|
||||||
|
with open('experiment_results.csv', 'w', newline='', encoding='utf-8') as f:
|
||||||
|
writer = csv.DictWriter(f, fieldnames=['maze', 'algorithm', 'avg_time_ms', 'avg_visited_cells', 'avg_path_length'])
|
||||||
|
writer.writeheader()
|
||||||
|
writer.writerows(results)
|
||||||
|
|
||||||
|
print("\nResults saved to experiment_results.csv")
|
||||||
|
|
||||||
|
|
||||||
|
def interactive_mode():
|
||||||
|
builder = TextFileMazeBuilder()
|
||||||
|
|
||||||
|
print("Interactive Maze Explorer")
|
||||||
|
print("1. Load maze from file")
|
||||||
|
print("2. Generate random maze")
|
||||||
|
choice = input("Choose (1/2): ")
|
||||||
|
|
||||||
|
if choice == '1':
|
||||||
|
filename = input("Enter filename: ")
|
||||||
|
try:
|
||||||
|
maze = builder.build_from_file(filename)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error loading maze: {e}")
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
w = int(input("Width: "))
|
||||||
|
h = int(input("Height: "))
|
||||||
|
maze = generate_test_maze(w, h, 0.3)
|
||||||
|
|
||||||
|
player = Player(maze.start)
|
||||||
|
|
||||||
|
strategies = {
|
||||||
|
'1': BFSStrategy(),
|
||||||
|
'2': DFSStrategy(),
|
||||||
|
'3': AStarStrategy(),
|
||||||
|
'4': DijkstraStrategy()
|
||||||
|
}
|
||||||
|
|
||||||
|
print("\nSelect algorithm for solving:")
|
||||||
|
print("1. BFS (shortest path)")
|
||||||
|
print("2. DFS (fast, not optimal)")
|
||||||
|
print("3. A* (heuristic)")
|
||||||
|
print("4. Dijkstra")
|
||||||
|
algo_choice = input("Choose: ")
|
||||||
|
|
||||||
|
solver = MazeSolver(maze, strategies.get(algo_choice, BFSStrategy()))
|
||||||
|
path, stats = solver.solve()
|
||||||
|
|
||||||
|
view = ConsoleView()
|
||||||
|
|
||||||
|
if path:
|
||||||
|
print(f"\nPath found! Length: {len(path)}")
|
||||||
|
view.show_stats(stats, solver.strategy.get_name())
|
||||||
|
else:
|
||||||
|
print("\nNo path found!")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
view.render(maze, player, path if path else None)
|
||||||
|
|
||||||
|
if player.current_cell == maze.exit:
|
||||||
|
print("Congratulations! You reached the exit!")
|
||||||
|
break
|
||||||
|
|
||||||
|
cmd = input("Move (W/A/S/D) | U=undo | Q=quit | S=solve: ").upper()
|
||||||
|
|
||||||
|
if cmd == 'Q':
|
||||||
|
break
|
||||||
|
elif cmd == 'U':
|
||||||
|
player.undo()
|
||||||
|
print("Undo last move")
|
||||||
|
elif cmd == 'S' and path:
|
||||||
|
for cell in path:
|
||||||
|
if cell == player.current_cell:
|
||||||
|
continue
|
||||||
|
player.move_to(cell)
|
||||||
|
view.render(maze, player, path)
|
||||||
|
input("Press Enter to continue...")
|
||||||
|
if player.current_cell == maze.exit:
|
||||||
|
print("You reached the exit!")
|
||||||
|
break
|
||||||
|
elif cmd in ['W', 'A', 'S', 'D']:
|
||||||
|
move_cmd = MoveCommand(player, maze, cmd)
|
||||||
|
if move_cmd.execute():
|
||||||
|
print("Moved")
|
||||||
|
else:
|
||||||
|
print("Can't move there!")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
print("Maze Solver with Design Patterns")
|
||||||
|
print("1. Run experiments")
|
||||||
|
print("2. Interactive mode")
|
||||||
|
choice = input("Choose (1/2): ")
|
||||||
|
|
||||||
|
if choice == '1':
|
||||||
|
run_experiments()
|
||||||
|
else:
|
||||||
|
interactive_mode()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
21
BobrovKN/zadanie/zad 2/experiment_results2.csv
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
maze,algorithm,avg_time_ms,avg_visited_cells,avg_path_length
|
||||||
|
Small (10x10),BFS,0.006740167737007141,80.0,0.0
|
||||||
|
Small (10x10),DFS,0.00408003106713295,80.0,0.0
|
||||||
|
Small (10x10),A*,0.005039852112531662,80.0,0.0
|
||||||
|
Small (10x10),Dijkstra,0.0031800009310245514,80.0,0.0
|
||||||
|
Medium (50x50),BFS,3.44578018411994,1890.0,99.0
|
||||||
|
Medium (50x50),DFS,1.3188599608838558,1890.0,341.0
|
||||||
|
Medium (50x50),A*,2.061920054256916,1890.0,99.0
|
||||||
|
Medium (50x50),Dijkstra,4.679400008171797,1890.0,99.0
|
||||||
|
Large (100x100),BFS,0.025319866836071014,6998.0,0.0
|
||||||
|
Large (100x100),DFS,0.019940081983804703,6998.0,0.0
|
||||||
|
Large (100x100),A*,0.035060010850429535,6998.0,0.0
|
||||||
|
Large (100x100),Dijkstra,0.02901991829276085,6998.0,0.0
|
||||||
|
Empty (30x30),BFS,1.2404202483594418,900.0,59.0
|
||||||
|
Empty (30x30),DFS,0.8887200616300106,900.0,465.0
|
||||||
|
Empty (30x30),A*,0.9468601085245609,900.0,59.0
|
||||||
|
Empty (30x30),Dijkstra,2.678940072655678,900.0,59.0
|
||||||
|
No Exit (20x20),BFS,0.27012014761567116,380.0,0.0
|
||||||
|
No Exit (20x20),DFS,0.3163599409162998,380.0,0.0
|
||||||
|
No Exit (20x20),A*,0.5885399878025055,380.0,0.0
|
||||||
|
No Exit (20x20),Dijkstra,0.5776201374828815,380.0,0.0
|
||||||
|
196
BobrovKN/zadanie/zad 2/otchet2.txt
Normal file
|
|
@ -0,0 +1,196 @@
|
||||||
|
Методы программирования
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Поиск выхода из лабиринта.
|
||||||
|
Анализ 2 задания
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Бобров К. Н.
|
||||||
|
425 группа
|
||||||
|
|
||||||
|
|
||||||
|
Содержание
|
||||||
|
|
||||||
|
Описание задачи и выбранных паттернов 2
|
||||||
|
Листинги ключевых классов 4
|
||||||
|
Результаты экспериментов 6
|
||||||
|
Анализ эффективности алгоритмов и применимости паттернов 7
|
||||||
|
Выводы 9
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Описание задачи и выбранных паттернов
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Описание задачи: реализовать систему для загрузки лабиринтов из файлов, поиска пути от старта до выхода с использованием различных алгоритмов, сбора статистики и визуализации. Ключевые требования — гибкость, расширяемость и возможность динамической смены алгоритмов.
|
||||||
|
|
||||||
|
Выбранные паттерны:
|
||||||
|
|
||||||
|
?Builder - Скрывает сложность создания лабиринта из текстового файла (парсинг, валидация, установка флагов). Позволяет легко добавить поддержку других форматов (JSON, XML).
|
||||||
|
?Strategy - Определяет семейство алгоритмов поиска пути (BFS, DFS, A*, Дейкстра), инкапсулирует каждый из них и делает их взаимозаменяемыми. Клиент (MazeSolver) может переключать стратегии во время выполнения.
|
||||||
|
?Observer - Обеспечивает реактивное обновление консольного интерфейса при изменениях (загрузка лабиринта, перемещение игрока, найденный путь). Позволяет добавить другие способы визуализации (GUI, логирование) без изменения бизнес-логики.
|
||||||
|
?Command - Реализует пошаговое управление игроком с возможностью отмены (undo). Позволяет сохранять историю команд и поддерживать транзакционность.
|
||||||
|
|
||||||
|
|
||||||
|
Листинги ключевых классов
|
||||||
|
|
||||||
|
Builder (TextFileMazeBuilder):
|
||||||
|
class TextFileMazeBuilder(MazeBuilder):
|
||||||
|
def build_from_file(self, filename: str) -> Maze:
|
||||||
|
with open(filename, 'r', encoding='utf-8') as f:
|
||||||
|
lines = [line.rstrip('\n') for line in f.readlines()]
|
||||||
|
|
||||||
|
height = len(lines)
|
||||||
|
width = max(len(line) for line in lines) if height > 0 else 0
|
||||||
|
maze = Maze(width, height)
|
||||||
|
|
||||||
|
for y, line in enumerate(lines):
|
||||||
|
for x, ch in enumerate(line):
|
||||||
|
cell = maze.get_cell(x, y)
|
||||||
|
if cell is None:
|
||||||
|
continue
|
||||||
|
if ch == '#':
|
||||||
|
cell.is_wall = True
|
||||||
|
elif ch == 'S':
|
||||||
|
cell.is_start = True
|
||||||
|
maze.start = cell
|
||||||
|
elif ch == 'E':
|
||||||
|
cell.is_exit = True
|
||||||
|
maze.exit = cell
|
||||||
|
elif ch == ' ':
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Unknown character '{ch}' at ({x},{y})")
|
||||||
|
|
||||||
|
if maze.start is None or maze.exit is None:
|
||||||
|
raise ValueError("Maze must have start (S) and exit (E)")
|
||||||
|
return maze
|
||||||
|
Strategy (пример BFS):
|
||||||
|
class BFSStrategy(PathFindingStrategy):
|
||||||
|
def find_path(self, maze: Maze, start: Cell, exit: Cell) -> List[Cell]:
|
||||||
|
queue = deque([start])
|
||||||
|
came_from = {start: None}
|
||||||
|
|
||||||
|
while queue:
|
||||||
|
current = queue.popleft()
|
||||||
|
if current == exit:
|
||||||
|
break
|
||||||
|
for nb in maze.get_neighbors(current):
|
||||||
|
if nb not in came_from:
|
||||||
|
came_from[nb] = current
|
||||||
|
queue.append(nb)
|
||||||
|
|
||||||
|
if exit not in came_from:
|
||||||
|
return []
|
||||||
|
|
||||||
|
path = []
|
||||||
|
cur = exit
|
||||||
|
while cur:
|
||||||
|
path.append(cur)
|
||||||
|
cur = came_from[cur]
|
||||||
|
path.reverse()
|
||||||
|
return path
|
||||||
|
|
||||||
|
def get_name(self) -> str:
|
||||||
|
return "BFS"
|
||||||
|
Observer (ConsoleView):
|
||||||
|
class ConsoleView:
|
||||||
|
@staticmethod
|
||||||
|
def render(maze: Maze, player: Optional[Player] = None, path: Optional[List[Cell]] = None):
|
||||||
|
path_set = set()
|
||||||
|
if path:
|
||||||
|
path_set = set(path)
|
||||||
|
|
||||||
|
for y in range(maze.height):
|
||||||
|
line = ""
|
||||||
|
for x in range(maze.width):
|
||||||
|
cell = maze.get_cell(x, y)
|
||||||
|
if not cell:
|
||||||
|
line += " "
|
||||||
|
elif player and player.current_cell == cell:
|
||||||
|
line += "P"
|
||||||
|
elif cell.is_start:
|
||||||
|
line += "S"
|
||||||
|
elif cell.is_exit:
|
||||||
|
line += "E"
|
||||||
|
elif cell.is_wall:
|
||||||
|
line += "#"
|
||||||
|
elif path and cell in path_set:
|
||||||
|
line += "."
|
||||||
|
else:
|
||||||
|
line += " "
|
||||||
|
print(line)
|
||||||
|
print()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def show_stats(stats: SearchStats, algo_name: str):
|
||||||
|
print(f"=== {algo_name} Results ===")
|
||||||
|
print(stats)
|
||||||
|
print()
|
||||||
|
|
||||||
|
|
||||||
|
Результаты экспериментов (таблицы, графики).
|
||||||
|
maze_type algorithm avg_time avg_visited_cells avg_path_len
|
||||||
|
small_10x10 BFS 0.08572000006097369 79.0 19.0
|
||||||
|
small_10x10 DFS 0.039739999920129776 79.0 31.0
|
||||||
|
small_10x10_ A* 0.13467999997374136 79.0 19.0
|
||||||
|
small_10x10 Dijkstra 0.11474000057205558 79.0 19.0
|
||||||
|
medium_50x50 BFS 1.8074600004183594 1874.0 99.0
|
||||||
|
medium_50x50 DFS 0.5937599995377241 1874.0 429.0
|
||||||
|
medium_50x50 A* 1.6300600003887666 1874.0 99.0
|
||||||
|
medium_50x50 Dijkstra 3.1870400001935195 1874.0 99.0
|
||||||
|
large_100x100 BFS 0.014439999722526409 7033.0 0.0
|
||||||
|
large_100x100 DFS 0.014839999857940711 7033.0 0.0
|
||||||
|
large_100x100 A* 0.02542000001994893 7033.0 0.0
|
||||||
|
large_100x100 Dijkstra 0.02548000011302065 7033.0 0.0
|
||||||
|
empty_30x30 BFS 0.784620000194991 900.0 59.0
|
||||||
|
empty_30x30 DFS 0.5252399994787993 900.0 465.
|
||||||
|
empty_30x30 A* 1.150900000357069 900.0 59.0
|
||||||
|
empty_30x30 Dijkstra 1.564640000287909 900.0 59.0
|
||||||
|
no_exit_20x20 BFS 0.2002399993216386 380. 0.0
|
||||||
|
no_exit_20x20 DFS 0.2512400002160575 380.0 0.0
|
||||||
|
no_exit_20x20 A* 0.5590400000073714 380.0 0.
|
||||||
|
no_exit_20x20 Dijkstra 0.35640000060084276 380.0 0.0
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Графики построены кодом из файла RESULT22.
|
||||||
|
|
||||||
|
Анализ эффективности алгоритмов и применимости паттернов
|
||||||
|
|
||||||
|
Анализ алгоритмов поиска пути
|
||||||
|
?BFS гарантированно находит кратчайший путь по количеству шагов, но в больших лабиринтах (особенно пустых или сильно ветвящихся) посещает очень много клеток. Время работы растёт пропорционально числу достижимых клеток.
|
||||||
|
?DFS быстро находит какой-либо путь, однако он часто оказывается неоптимальным (длиннее возможного минимума). В лабиринтах с тупиками может уходить в глубокую рекурсию, что приводит к большому количеству посещённых клеток.
|
||||||
|
?A с манхэттенской эвристикой* показывает наилучшую эффективность на сложных лабиринтах: посещает значительно меньше клеток, чем BFS, и при этом даёт оптимальный путь (благодаря допустимости эвристики). В пустом лабиринте работает аналогично BFS, но с небольшими дополнительными накладными расходами на поддержку очереди с приоритетом.
|
||||||
|
?Алгоритм Дейкстры при единичных весах рёбер эквивалентен BFS по результату, но работает медленнее из-за использования кучи. Он становится полезным во взвешенных лабиринтах (например, с болотами или песком), где BFS даёт неоптимальную стоимость пути.
|
||||||
|
Применимость паттернов проектирования
|
||||||
|
?Builder позволил полностью изолировать формат ввода данных, скрыв детали парсинга от основной логики.
|
||||||
|
?Strategy обеспечил возможность переключения алгоритмов во время выполнения (например, в MazeSolver). Без этого паттерна пришлось бы использовать условные операторы или наследование, что нарушило бы принцип открытости/закрытости.
|
||||||
|
?Observer отделил визуализацию от бизнес-логики. При замене консольного вывода на PyQt или веб-интерфейс достаточно реализовать нового наблюдателя — остальной код не требует изменений.
|
||||||
|
?Command упростил реализацию отмены/возврата действий (undo/redo) и позволил добавлять макрокоманды (например, автоматическое прохождение по найденному пути) без модификации существующих классов.
|
||||||
|
|
||||||
|
Выводы
|
||||||
|
Достигнутые преимущества
|
||||||
|
Применение объектно-ориентированного подхода и паттернов проектирования обеспечило:
|
||||||
|
1.Гибкость — легко добавить новый алгоритм поиска (например, волновой алгоритм) или новый формат лабиринта.
|
||||||
|
2.Расширяемость — для интеграции графического интерфейса достаточно реализовать ещё одного наблюдателя, не изменяя MazeSolver и существующие стратегии.
|
||||||
|
3.Поддерживаемость — каждый паттерн инкапсулирует ровно одну изменяющуюся характеристику: создание объектов, алгоритм поиска, механизм уведомлений, выполняемые действия.
|
||||||
|
4.Тестируемость — стратегии можно тестировать изолированно друг от друга, подставляя mock-объекты там, где это необходимо.
|
||||||
|
Что потребовало бы больших усилий без паттернов
|
||||||
|
?Смена алгоритма поиска во время выполнения потребовала бы переписывания кода MazeSolver и внедрения громоздких условных операторов.
|
||||||
|
?Добавление нового формата лабиринта затронуло бы логику парсинга во многих местах, если бы она была размазана по всему коду, а не вынесена в отдельный строитель (Builder).
|
||||||
|
?Реализация отмены действий (undo) потребовала бы жёсткой привязки к конкретным командам и нарушения инкапсуляции игрока.
|
||||||
|
?Визуализация оказалась бы жёстко связанной с бизнес-логикой, что серьёзно усложнило бы переход на другой интерфейс (например, с консоли на PyQt или веб).
|
||||||
|
Общий вывод
|
||||||
|
Паттерны проектирования в полной мере оправдали своё применение в данном проекте: система стала легко расширяемой, хорошо структурированной и готовой к будущим изменениям без необходимости переписывать существующий код.
|
||||||
0
BobrovKN/zadanie/БББ.txt
Normal file
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
477
BorisovMI/lab_1/docs/data/structures.py
Normal file
|
|
@ -0,0 +1,477 @@
|
||||||
|
import time
|
||||||
|
import random
|
||||||
|
import csv
|
||||||
|
import os
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import numpy as np
|
||||||
|
import sys
|
||||||
|
sys.setrecursionlimit(20000)
|
||||||
|
# Связный список
|
||||||
|
|
||||||
|
def ll_insert(head, name, phone):
|
||||||
|
|
||||||
|
new_node = {'name': name, 'phone': phone, 'next': None}
|
||||||
|
|
||||||
|
if head is None:
|
||||||
|
return new_node
|
||||||
|
|
||||||
|
if head['name'] == name:
|
||||||
|
head['phone'] = phone
|
||||||
|
return head
|
||||||
|
|
||||||
|
current = head
|
||||||
|
while current['next'] is not None:
|
||||||
|
if current['next']['name'] == name:
|
||||||
|
current['next']['phone'] = phone
|
||||||
|
return head
|
||||||
|
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']
|
||||||
|
|
||||||
|
current = head
|
||||||
|
while current['next'] is not None:
|
||||||
|
if current['next']['name'] == name:
|
||||||
|
current['next'] = current['next']['next']
|
||||||
|
return head
|
||||||
|
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 x: x[0])
|
||||||
|
return records
|
||||||
|
|
||||||
|
# Хэш функции
|
||||||
|
|
||||||
|
def hash_function(name, table_size):
|
||||||
|
return sum(ord(c) for c in name) % table_size
|
||||||
|
|
||||||
|
|
||||||
|
def ht_create(size=1000):
|
||||||
|
return [None] * size
|
||||||
|
|
||||||
|
|
||||||
|
def ht_insert(buckets, name, phone):
|
||||||
|
size = len(buckets)
|
||||||
|
index = hash_function(name, size)
|
||||||
|
buckets[index] = ll_insert(buckets[index], name, phone)
|
||||||
|
|
||||||
|
|
||||||
|
def ht_find(buckets, name):
|
||||||
|
size = len(buckets)
|
||||||
|
index = hash_function(name, size)
|
||||||
|
return ll_find(buckets[index], name)
|
||||||
|
|
||||||
|
|
||||||
|
def ht_delete(buckets, name):
|
||||||
|
size = len(buckets)
|
||||||
|
index = hash_function(name, size)
|
||||||
|
buckets[index] = ll_delete(buckets[index], name)
|
||||||
|
|
||||||
|
|
||||||
|
def ht_list_all(buckets):
|
||||||
|
records = []
|
||||||
|
for bucket in buckets:
|
||||||
|
current = bucket
|
||||||
|
while current is not None:
|
||||||
|
records.append((current['name'], current['phone']))
|
||||||
|
current = current['next']
|
||||||
|
records.sort(key=lambda x: x[0])
|
||||||
|
return records
|
||||||
|
|
||||||
|
# Функции для дерева поиска
|
||||||
|
|
||||||
|
def bst_insert(root, name, phone):
|
||||||
|
|
||||||
|
if root is None:
|
||||||
|
return {'name': name, 'phone': phone, 'left': None, 'right': None}
|
||||||
|
|
||||||
|
if name < root['name']:
|
||||||
|
root['left'] = bst_insert(root['left'], name, phone)
|
||||||
|
elif name > root['name']:
|
||||||
|
root['right'] = bst_insert(root['right'], name, phone)
|
||||||
|
else:
|
||||||
|
root['phone'] = phone
|
||||||
|
|
||||||
|
return root
|
||||||
|
|
||||||
|
|
||||||
|
def bst_find(root, name):
|
||||||
|
|
||||||
|
current = root
|
||||||
|
while current is not None:
|
||||||
|
if name == current['name']:
|
||||||
|
return current['phone']
|
||||||
|
elif name < current['name']:
|
||||||
|
current = current['left']
|
||||||
|
else:
|
||||||
|
current = current['right']
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def bst_find_min(node):
|
||||||
|
|
||||||
|
current = node
|
||||||
|
while current['left'] is not None:
|
||||||
|
current = current['left']
|
||||||
|
return current
|
||||||
|
|
||||||
|
|
||||||
|
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']
|
||||||
|
elif root['right'] is None:
|
||||||
|
return root['left']
|
||||||
|
|
||||||
|
min_node = bst_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):
|
||||||
|
|
||||||
|
records = []
|
||||||
|
|
||||||
|
def inorder_traversal(node):
|
||||||
|
if node is not None:
|
||||||
|
inorder_traversal(node['left'])
|
||||||
|
records.append((node['name'], node['phone']))
|
||||||
|
inorder_traversal(node['right'])
|
||||||
|
|
||||||
|
inorder_traversal(root)
|
||||||
|
return records
|
||||||
|
|
||||||
|
# Генерация тестовых данных
|
||||||
|
|
||||||
|
def generate_records(count=10000):
|
||||||
|
|
||||||
|
records = []
|
||||||
|
for i in range(count):
|
||||||
|
name = f"User_{i:05d}"
|
||||||
|
phone = f"+7-{random.randint(100,999)}-{random.randint(100,999)}-{random.randint(1000,9999)}"
|
||||||
|
records.append((name, phone))
|
||||||
|
|
||||||
|
shuffled = records.copy()
|
||||||
|
random.shuffle(shuffled)
|
||||||
|
sorted_records = sorted(records, key=lambda x: x[0])
|
||||||
|
|
||||||
|
return shuffled, sorted_records
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Замер времени
|
||||||
|
|
||||||
|
def measure_insertion(structure_name, records):
|
||||||
|
|
||||||
|
times = []
|
||||||
|
filled_structure = None
|
||||||
|
|
||||||
|
for run in range(5):
|
||||||
|
if structure_name == "linked_list":
|
||||||
|
structure = None
|
||||||
|
elif structure_name == "hash_table":
|
||||||
|
structure = ht_create(1000)
|
||||||
|
elif structure_name == "bst":
|
||||||
|
structure = None
|
||||||
|
|
||||||
|
start = time.perf_counter()
|
||||||
|
|
||||||
|
for name, phone in records:
|
||||||
|
if structure_name == "linked_list":
|
||||||
|
structure = ll_insert(structure, name, phone)
|
||||||
|
elif structure_name == "hash_table":
|
||||||
|
ht_insert(structure, name, phone)
|
||||||
|
elif structure_name == "bst":
|
||||||
|
structure = bst_insert(structure, name, phone)
|
||||||
|
|
||||||
|
end = time.perf_counter()
|
||||||
|
times.append(end - start)
|
||||||
|
|
||||||
|
if run == 4:
|
||||||
|
filled_structure = structure
|
||||||
|
|
||||||
|
return times, filled_structure
|
||||||
|
|
||||||
|
def measure_search(structure_name, structure, search_names):
|
||||||
|
|
||||||
|
times = []
|
||||||
|
|
||||||
|
for run in range(5):
|
||||||
|
start = time.perf_counter()
|
||||||
|
|
||||||
|
for name in search_names:
|
||||||
|
if structure_name == "linked_list":
|
||||||
|
ll_find(structure, name)
|
||||||
|
elif structure_name == "hash_table":
|
||||||
|
ht_find(structure, name)
|
||||||
|
elif structure_name == "bst":
|
||||||
|
bst_find(structure, name)
|
||||||
|
|
||||||
|
end = time.perf_counter()
|
||||||
|
times.append(end - start)
|
||||||
|
|
||||||
|
return times
|
||||||
|
|
||||||
|
def measure_deletion(structure_name, original_structure, delete_names):
|
||||||
|
|
||||||
|
times = []
|
||||||
|
|
||||||
|
for run in range(5):
|
||||||
|
if structure_name == "linked_list":
|
||||||
|
all_records = ll_list_all(original_structure)
|
||||||
|
test_structure = None
|
||||||
|
for name, phone in all_records:
|
||||||
|
test_structure = ll_insert(test_structure, name, phone)
|
||||||
|
|
||||||
|
elif structure_name == "hash_table":
|
||||||
|
all_records = ht_list_all(original_structure)
|
||||||
|
test_structure = ht_create(1000)
|
||||||
|
for name, phone in all_records:
|
||||||
|
ht_insert(test_structure, name, phone)
|
||||||
|
|
||||||
|
elif structure_name == "bst":
|
||||||
|
all_records = bst_list_all(original_structure)
|
||||||
|
test_structure = None
|
||||||
|
for name, phone in all_records:
|
||||||
|
test_structure = bst_insert(test_structure, name, phone)
|
||||||
|
|
||||||
|
start = time.perf_counter()
|
||||||
|
|
||||||
|
for name in delete_names:
|
||||||
|
if structure_name == "linked_list":
|
||||||
|
test_structure = ll_delete(test_structure, name)
|
||||||
|
elif structure_name == "hash_table":
|
||||||
|
ht_delete(test_structure, name)
|
||||||
|
elif structure_name == "bst":
|
||||||
|
test_structure = bst_delete(test_structure, name)
|
||||||
|
|
||||||
|
end = time.perf_counter()
|
||||||
|
times.append(end - start)
|
||||||
|
|
||||||
|
return times
|
||||||
|
|
||||||
|
# Основная часть
|
||||||
|
|
||||||
|
def run_experiment():
|
||||||
|
|
||||||
|
current_dir = os.path.dirname(__name__)
|
||||||
|
docs_dir = os.path.dirname(current_dir)
|
||||||
|
csv_file = os.path.join(docs_dir, "experiment_results.csv")
|
||||||
|
|
||||||
|
print("=" * 70)
|
||||||
|
print("ЭКСПЕРИМЕНТАЛЬНОЕ СРАВНЕНИЕ СТРУКТУР ДАННЫХ")
|
||||||
|
print("Телефонный справочник - 10000 записей")
|
||||||
|
print("=" * 70)
|
||||||
|
print(f"\nРезультаты будут сохранены в: {csv_file}")
|
||||||
|
|
||||||
|
print("\n1. Генерация тестовых данных...")
|
||||||
|
shuffled_records, sorted_records = generate_records(10000)
|
||||||
|
print(f" Сгенерировано 10000 записей")
|
||||||
|
|
||||||
|
existing_names = [shuffled_records[i][0] for i in random.sample(range(10000), 100)]
|
||||||
|
nonexisting_names = [f"NotExist_{i}" for i in range(10)]
|
||||||
|
search_names = existing_names + nonexisting_names
|
||||||
|
delete_names = [shuffled_records[i][0] for i in random.sample(range(10000), 50)]
|
||||||
|
|
||||||
|
results = [["Структура", "Режим", "Операция",
|
||||||
|
"Замер1(с)", "Замер2(с)", "Замер3(с)", "Замер4(с)", "Замер5(с)",
|
||||||
|
"Среднее(с)"]]
|
||||||
|
|
||||||
|
for mode_name, records in [("случайный", shuffled_records),
|
||||||
|
("отсортированный", sorted_records)]:
|
||||||
|
|
||||||
|
print(f"\n2. Тестирование режима: {mode_name}")
|
||||||
|
print("-" * 50)
|
||||||
|
|
||||||
|
for struct_name in ["linked_list", "hash_table", "bst"]:
|
||||||
|
print(f"\n {struct_name.upper()}:")
|
||||||
|
|
||||||
|
print(" Вставка 10000 записей...")
|
||||||
|
insert_times, filled_struct = measure_insertion(struct_name, records)
|
||||||
|
avg_insert = sum(insert_times) / 5
|
||||||
|
print(f" Время: {avg_insert:.4f} сек (среднее)")
|
||||||
|
|
||||||
|
print(" Поиск 110 записей...")
|
||||||
|
search_times = measure_search(struct_name, filled_struct, search_names)
|
||||||
|
avg_search = sum(search_times) / 5
|
||||||
|
print(f" Время: {avg_search:.4f} сек (среднее)")
|
||||||
|
|
||||||
|
print(" Удаление 50 записей...")
|
||||||
|
delete_times = measure_deletion(struct_name, filled_struct, delete_names)
|
||||||
|
avg_delete = sum(delete_times) / 5
|
||||||
|
print(f" Время: {avg_delete:.4f} сек (среднее)")
|
||||||
|
|
||||||
|
results.append([struct_name, mode_name, "вставка"] + insert_times + [avg_insert])
|
||||||
|
results.append([struct_name, mode_name, "поиск"] + search_times + [avg_search])
|
||||||
|
results.append([struct_name, mode_name, "удаление"] + delete_times + [avg_delete])
|
||||||
|
|
||||||
|
print("\n3. Сохранение результатов...")
|
||||||
|
with open(csv_file, "w", newline="", encoding="utf-8") as f:
|
||||||
|
writer = csv.writer(f)
|
||||||
|
writer.writerows(results)
|
||||||
|
print(f" Результаты сохранены в: {csv_file}")
|
||||||
|
|
||||||
|
print("\n" + "=" * 70)
|
||||||
|
print("СВОДНАЯ ТАБЛИЦА РЕЗУЛЬТАТОВ")
|
||||||
|
print("=" * 70)
|
||||||
|
print(f"{'Структура':<15} {'Режим':<12} {'Операция':<10} {'Среднее время (сек)':<20}")
|
||||||
|
print("-" * 70)
|
||||||
|
|
||||||
|
for row in results[1:]:
|
||||||
|
struct, mode, op, t1, t2, t3, t4, t5, avg = row
|
||||||
|
print(f"{struct:<15} {mode:<12} {op:<10} {avg:<20.6f}")
|
||||||
|
|
||||||
|
return results, docs_dir
|
||||||
|
|
||||||
|
# Графики
|
||||||
|
|
||||||
|
def create_graphs(results, docs_dir):
|
||||||
|
|
||||||
|
print("\n4. Построение графиков...")
|
||||||
|
|
||||||
|
data = {}
|
||||||
|
for row in results[1:]:
|
||||||
|
struct = row[0]
|
||||||
|
mode = row[1]
|
||||||
|
op = row[2]
|
||||||
|
avg = row[8]
|
||||||
|
|
||||||
|
if struct not in data:
|
||||||
|
data[struct] = {}
|
||||||
|
if mode not in data[struct]:
|
||||||
|
data[struct][mode] = {}
|
||||||
|
data[struct][mode][op] = avg
|
||||||
|
|
||||||
|
|
||||||
|
struct_labels = {
|
||||||
|
'linked_list': 'LinkedList',
|
||||||
|
'hash_table': 'HashTable',
|
||||||
|
'bst': 'BST'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
colors = {
|
||||||
|
'linked_list': '#3498db',
|
||||||
|
'hash_table': '#2ecc71',
|
||||||
|
'bst': '#e74c3c'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fig, axes = plt.subplots(1, 3, figsize=(15, 6))
|
||||||
|
fig.suptitle('Сравнение производительности структур данных', fontsize=16, fontweight='bold')
|
||||||
|
|
||||||
|
operations = ['вставка', 'поиск', 'удаление']
|
||||||
|
operation_titles = ['Вставка\n(10000 записей)', 'Поиск\n(110 запросов)', 'Удаление\n(50 записей)']
|
||||||
|
modes = ['случайный', 'отсортированный']
|
||||||
|
mode_labels = ['Случайный', 'Отсортированный']
|
||||||
|
|
||||||
|
for idx, (op, op_title) in enumerate(zip(operations, operation_titles)):
|
||||||
|
ax = axes[idx]
|
||||||
|
|
||||||
|
# Позиции для групп столбцов
|
||||||
|
x = np.arange(len(modes)) # [0, 1]
|
||||||
|
width = 0.25 # ширина одного столбца
|
||||||
|
multiplier = 0
|
||||||
|
|
||||||
|
for struct in ['linked_list', 'hash_table', 'bst']:
|
||||||
|
values = [data[struct][mode][op] for mode in modes]
|
||||||
|
offset = width * multiplier
|
||||||
|
bars = ax.bar(x + offset, values, width,
|
||||||
|
label=struct_labels[struct],
|
||||||
|
color=colors[struct],
|
||||||
|
edgecolor='black', linewidth=0.5)
|
||||||
|
|
||||||
|
|
||||||
|
for bar, val in zip(bars, values):
|
||||||
|
if val < 0.01:
|
||||||
|
ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + val*0.05,
|
||||||
|
f'{val:.5f}', ha='center', va='bottom', fontsize=8, rotation=0)
|
||||||
|
else:
|
||||||
|
ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + val*0.02,
|
||||||
|
f'{val:.4f}', ha='center', va='bottom', fontsize=8, rotation=0)
|
||||||
|
|
||||||
|
multiplier += 1
|
||||||
|
|
||||||
|
|
||||||
|
ax.set_title(op_title, fontsize=12, fontweight='bold')
|
||||||
|
ax.set_ylabel('Время (секунды)', fontsize=10)
|
||||||
|
ax.set_xlabel('Режим данных', fontsize=10)
|
||||||
|
ax.set_xticks(x + width)
|
||||||
|
ax.set_xticklabels(mode_labels)
|
||||||
|
ax.legend(loc='upper left', fontsize=8)
|
||||||
|
ax.grid(True, alpha=0.3, axis='y')
|
||||||
|
|
||||||
|
|
||||||
|
all_values = [data[s][m][op] for s in ['linked_list', 'hash_table', 'bst'] for m in modes]
|
||||||
|
if max(all_values) / min(all_values) > 100:
|
||||||
|
ax.set_yscale('log')
|
||||||
|
ax.set_ylabel('Время (секунды) - логарифмическая шкала', fontsize=9)
|
||||||
|
|
||||||
|
plt.tight_layout()
|
||||||
|
graph_path = os.path.join(docs_dir, "performance_graphs.png")
|
||||||
|
plt.savefig(graph_path, dpi=150, bbox_inches='tight')
|
||||||
|
plt.close()
|
||||||
|
print(f" Графики сохранены в: {graph_path}")
|
||||||
|
|
||||||
|
return graph_path
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
|
||||||
|
results, docs_dir = run_experiment()
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
graph_file = create_graphs(results, docs_dir)
|
||||||
|
|
||||||
|
print("\n" + "=" * 70)
|
||||||
|
print("ЭКСПЕРИМЕНТ ЗАВЕРШЕН УСПЕШНО!")
|
||||||
|
print("=" * 70)
|
||||||
|
print("\nСОЗДАННЫЕ ФАЙЛЫ:")
|
||||||
|
print(f" Данные: {os.path.join(docs_dir, 'experiment_results.csv')}")
|
||||||
|
print(f" Графики: {graph_file}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"\nОшибка при построении графиков: {e}")
|
||||||
|
print(" Убедитесь, что установлен matplotlib: pip install matplotlib")
|
||||||
|
print("\n" + "=" * 70)
|
||||||
|
print("ЭКСПЕРИМЕНТ ЗАВЕРШЕН (без графиков)")
|
||||||
|
print("=" * 70)
|
||||||
|
print(f"\nCSV файл сохранен: {os.path.join(docs_dir, 'experiment_results.csv')}")
|
||||||
19
BorisovMI/lab_1/docs/experiment_results.csv
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
Структура,Режим,Операция,Замер1(с),Замер2(с),Замер3(с),Замер4(с),Замер5(с),Среднее(с)
|
||||||
|
linked_list,случайный,вставка,11.072574299992993,12.38099840003997,12.155311000009533,12.81002289999742,13.201067199988756,12.323994760005736
|
||||||
|
linked_list,случайный,поиск,0.1209169999929145,0.13400630000978708,0.1302103999769315,0.12179599999217317,0.13267739996081218,0.12792141998652368
|
||||||
|
linked_list,случайный,удаление,0.05278969998471439,0.05019940005149692,0.05289009999250993,0.0488043999648653,0.061356800026260316,0.05320808000396937
|
||||||
|
hash_table,случайный,вставка,0.694432099990081,0.7192347000236623,0.7134853000170551,0.6891152000171132,0.6973775000078604,0.7027289600111544
|
||||||
|
hash_table,случайный,поиск,0.006584499962627888,0.004749000014271587,0.004359900020062923,0.006477299961261451,0.006112299975939095,0.005656599986832589
|
||||||
|
hash_table,случайный,удаление,0.004070399969350547,0.004140400036703795,0.003542299964465201,0.0032371999695897102,0.0030583000043407083,0.0036097199888899924
|
||||||
|
bst,случайный,вставка,0.08137199998600408,0.08989329996984452,0.08159760001581162,0.07841239997651428,0.1001471999916248,0.08628449998795987
|
||||||
|
bst,случайный,поиск,0.0006561999907717109,0.00043180002830922604,0.00042429997120052576,0.0004227000172249973,0.00043509999522939324,0.0004740200005471706
|
||||||
|
bst,случайный,удаление,0.19723930000327528,0.166880399978254,0.147482400003355,0.18100009998306632,0.16498840000713244,0.1715181199950166
|
||||||
|
linked_list,отсортированный,вставка,12.294058899977244,11.597062399960123,11.59195000003092,11.623520500026643,11.401553199975751,11.701628999994137
|
||||||
|
linked_list,отсортированный,поиск,0.10066379996715114,0.0991929000010714,0.09696789999725297,0.09909789997618645,0.09873830003198236,0.09893215999472886
|
||||||
|
linked_list,отсортированный,удаление,0.05669830000260845,0.048193000024184585,0.05076910002389923,0.04920080001465976,0.05905079998774454,0.05278240001061931
|
||||||
|
hash_table,отсортированный,вставка,0.5518350999918766,0.5407364999991842,0.5291212999727577,0.5373308000271209,0.529426000022795,0.5376899400027468
|
||||||
|
hash_table,отсортированный,поиск,0.005108800018206239,0.004848999960813671,0.004103399987798184,0.0038874000310897827,0.005848900007549673,0.00475950000109151
|
||||||
|
hash_table,отсортированный,удаление,0.0037329999613575637,0.003185699984896928,0.0028516000020317733,0.0030229000258259475,0.0037879000301472843,0.003316220000851899
|
||||||
|
bst,отсортированный,вставка,32.23637629998848,33.77348939998774,33.518200399994384,33.78866110002855,33.55791890004184,33.3749292200082
|
||||||
|
bst,отсортированный,поиск,0.1479294000309892,0.1305704999831505,0.13548930000979453,0.1314462999580428,0.130273999995552,0.1351418999955058
|
||||||
|
bst,отсортированный,удаление,0.1574716999894008,0.16461069998331368,0.15460990002611652,0.15385339997010306,0.15677610004786402,0.15746436000335962
|
||||||
|
BIN
BorisovMI/lab_1/docs/performance_graphs.png
Normal file
|
After Width: | Height: | Size: 117 KiB |
73
BorisovMI/lab_1/docs/report.md
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
# Отчёт: Задание 1 — Структуры данных
|
||||||
|
|
||||||
|
## Цель работы
|
||||||
|
|
||||||
|
Реализовать три структуры данных «с нуля» в процедурной парадигме (без классов), применить их для хранения записей телефонного справочника и экспериментально сравнить производительность основных операций.
|
||||||
|
|
||||||
|
## Описание реализованных структур
|
||||||
|
|
||||||
|
Связный список
|
||||||
|
- Узел: {'name': str, 'phone': str, 'next': dict или None}
|
||||||
|
- Операции проходят путём последовательного обхода элементов
|
||||||
|
- Подходит для небольших объёмов данных
|
||||||
|
|
||||||
|
Хеш-таблица
|
||||||
|
- Массив корзин фиксированного размера (1000)
|
||||||
|
- Хеш-функция: сумма кодов символов имени по модулю размера
|
||||||
|
- Разрешение коллизий: метод цепочек (связные списки)
|
||||||
|
|
||||||
|
Двоичное дерево поиска
|
||||||
|
- Узел: {'name': str, 'phone': str, 'left': dict, 'right': dict}
|
||||||
|
- Левое поддерево содержит меньшие значения
|
||||||
|
- Правое поддерево содержит большие значения
|
||||||
|
|
||||||
|
|
||||||
|
## Условия эксперимента
|
||||||
|
|
||||||
|
Общее количество записей - 10 000
|
||||||
|
Количество замеров для каждой операции - 5
|
||||||
|
Размер хеш-таблицы - 1000 корзин
|
||||||
|
Количество поисковых запросов - 110 (100 существующих + 10 несуществующих)
|
||||||
|
Количество удаляемых записей - 50
|
||||||
|
Режимы вставки данных - Случайный / Отсортированный
|
||||||
|
Инструмент замера времени - time.perf_counter()
|
||||||
|
|
||||||
|
|
||||||
|
## Результаты
|
||||||
|
|
||||||
|
### Таблица средних времён (секунды)
|
||||||
|
|
||||||
|
| Структура | Режим | Вставка (с) | Поиск 110 (с) | Удаление 50 (с) |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| LinkedList | случайный | 12.32399 | 0.1279214 | 0.053208 |
|
||||||
|
| LinkedList | сортированный | 11.701628 | 0.0989321 | 0.052782 |
|
||||||
|
| HashTable | случайный | 0.7027289 | 0.0056565 | 0.0036097 |
|
||||||
|
| HashTable | сортированный | 0.5376899 | 0.0047595 | 0.003316 |
|
||||||
|
| BST | случайный | 0.08628449 | 0.000474 | 0.1715181 |
|
||||||
|
| BST | сортированный | 33.374929 | 0.1351418 | 0.1574643 |
|
||||||
|
|
||||||
|
### Графики
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Анализ результатов
|
||||||
|
|
||||||
|
### Связный список
|
||||||
|
|
||||||
|
Вставка в список занимает ~2.5 секунды на 10 000 записей, потому что каждая вставка уже существующего имени требует прохода по всему списку O(n). При случайных уникальных именах вставка идёт в начало O(1), но поиск всегда линейный.
|
||||||
|
|
||||||
|
### Хеш-таблица
|
||||||
|
|
||||||
|
Хеш-таблица показала примерно одинаковые результаты при случайном и отсортированном порядке:
|
||||||
|
|
||||||
|
Это объясняется природой хеширования: порядок вставки не влияет на распределение по бакетам. Ключ всегда попадает в предсказуемый бакет за O(1).
|
||||||
|
|
||||||
|
### BST
|
||||||
|
|
||||||
|
деградирует на отсортированных данных
|
||||||
|
|
||||||
|
Причина:*при вставке отсортированных данных BST вырождается в односвязный список — каждый новый элемент больше предыдущего и уходит всегда в правое поддерево. Высота дерева становится O(n) вместо O(log n). Поиск и удаление тоже деградируют до O(n).
|
||||||
|
|
||||||
|
## Вывод
|
||||||
|
|
||||||
|
HashTable — лучший выбор для телефонного справочника при частых вставках и поисках. BST лучше HashTable только если нужен отсортированный вывод без дополнительной сортировки — но при условии случайного порядка вставки или использования самобалансирующегося дерева (AVL, Red-Black).
|
||||||
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 |
BIN
BrychkinKA/experiments/plot_time.png
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
BrychkinKA/experiments/plot_visited.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
16
BrychkinKA/experiments/results.csv
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
maze,algorithm,time_ms,visited,path_len
|
||||||
|
big.txt,BFS,0.14230050146579742,27,0
|
||||||
|
big.txt,DFS,0.1100003719329834,27,0
|
||||||
|
big.txt,A*,0.23249909281730652,27,0
|
||||||
|
empty.txt,BFS,0.07219985127449036,10,10
|
||||||
|
empty.txt,DFS,0.046100467443466187,10,10
|
||||||
|
empty.txt,A*,0.08819997310638428,10,10
|
||||||
|
medium.txt,BFS,0.09160116314888,21,17
|
||||||
|
medium.txt,DFS,0.07379986345767975,19,17
|
||||||
|
medium.txt,A*,0.15410035848617554,21,17
|
||||||
|
no_exit.txt,BFS,0.0007003545761108398,0,0
|
||||||
|
no_exit.txt,DFS,0.0027008354663848877,0,0
|
||||||
|
no_exit.txt,A*,0.0001993030309677124,0,0
|
||||||
|
small.txt,BFS,0.06789900362491608,7,7
|
||||||
|
small.txt,DFS,0.03989972174167633,7,7
|
||||||
|
small.txt,A*,0.09530037641525269,7,7
|
||||||
|
13
BrychkinKA/mazes/big.txt
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
####################################################################################################
|
||||||
|
#S # ########### # # ######### # #
|
||||||
|
# ####### ######### ########### ###### ######## ######## ######## ######## ######## ########## #####
|
||||||
|
# # # # # # # # # # # #
|
||||||
|
######## ######### ######### ######## ######## ######## ######## ######## ######## ######## #######
|
||||||
|
# # # # # # # # # # # # #
|
||||||
|
# ######## ##### # # ##### ######## ######## ######## ######## ######## ######## ######## #########
|
||||||
|
# # # # # # # # # # # # # #
|
||||||
|
######## ####### # ####### ######## ######## ######## ######## ######## ######## ######## #########
|
||||||
|
# # # # # # # # # # # #
|
||||||
|
# #### ######## ######## ######## ######## ######## ######## ######## ######## ######## ###########
|
||||||
|
# # # # # # # # # # # E#
|
||||||
|
####################################################################################################
|
||||||
1
BrychkinKA/mazes/empty.txt
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
S E
|
||||||
5
BrychkinKA/mazes/medium.txt
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
###############
|
||||||
|
#S # E#
|
||||||
|
# ### ####### #
|
||||||
|
# #
|
||||||
|
###############
|
||||||
3
BrychkinKA/mazes/no_exit.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
#######
|
||||||
|
#S #
|
||||||
|
#######
|
||||||
3
BrychkinKA/mazes/small.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
##########
|
||||||
|
#S E#
|
||||||
|
##########
|
||||||
7
BrychkinKA/src/builder/maze_builder.py
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from src.model.maze import Maze
|
||||||
|
|
||||||
|
class MazeBuilder(ABC):
|
||||||
|
@abstractmethod
|
||||||
|
def build_from_file(self, filename: str) -> Maze:
|
||||||
|
pass
|
||||||
36
BrychkinKA/src/builder/text_file_maze_builder.py
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
from src.model.cell import Cell
|
||||||
|
from src.model.maze import Maze
|
||||||
|
|
||||||
|
class TextFileMazeBuilder:
|
||||||
|
def build_from_file(self, filename):
|
||||||
|
with open(filename, "r", encoding="utf-8") as f:
|
||||||
|
lines = [line.rstrip("\n") for line in f]
|
||||||
|
|
||||||
|
height = len(lines)
|
||||||
|
width = max(len(line) for line in lines)
|
||||||
|
|
||||||
|
cells = []
|
||||||
|
start = None
|
||||||
|
exit_ = None
|
||||||
|
|
||||||
|
for y, line in enumerate(lines):
|
||||||
|
row = []
|
||||||
|
for x, ch in enumerate(line.ljust(width)):
|
||||||
|
is_wall = (ch == "#")
|
||||||
|
is_start = (ch == "S")
|
||||||
|
is_exit = (ch == "E")
|
||||||
|
|
||||||
|
cell = Cell(x, y, is_wall, is_start, is_exit)
|
||||||
|
row.append(cell)
|
||||||
|
|
||||||
|
if is_start:
|
||||||
|
start = cell
|
||||||
|
if is_exit:
|
||||||
|
exit_ = cell
|
||||||
|
|
||||||
|
cells.append(row)
|
||||||
|
|
||||||
|
if start is None:
|
||||||
|
raise ValueError("Файл должен содержать S (старт)")
|
||||||
|
|
||||||
|
return Maze(width, height, cells, start, exit_)
|
||||||
101
BrychkinKA/src/main.py
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
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
|
||||||
|
from src.ui.console_view import ConsoleView
|
||||||
|
from src.ui.player import Player
|
||||||
|
from src.ui.move_command import MoveCommand
|
||||||
|
|
||||||
|
|
||||||
|
def choose_maze():
|
||||||
|
mazes = {
|
||||||
|
"1": ("small.txt", "Small — маленький лабиринт"),
|
||||||
|
"2": ("medium.txt", "Medium — средний лабиринт"),
|
||||||
|
"3": ("big.txt", "Big — большой лабиринт(тупиковый)"),
|
||||||
|
"4": ("empty.txt", "Empty — пустой лабиринт"),
|
||||||
|
"5": ("no_exit.txt","NoExit — без выхода")
|
||||||
|
}
|
||||||
|
|
||||||
|
print("\n" + "=" * 40)
|
||||||
|
print(" ВЫБОР ЛАБИРИНТА")
|
||||||
|
print("=" * 40)
|
||||||
|
|
||||||
|
for key, (_, desc) in mazes.items():
|
||||||
|
print(f" {key}. {desc}")
|
||||||
|
|
||||||
|
print("=" * 40)
|
||||||
|
|
||||||
|
choice = input("Введите номер: ").strip()
|
||||||
|
|
||||||
|
if choice not in mazes:
|
||||||
|
print("Неверный выбор, загружаю small.txt")
|
||||||
|
return "small.txt"
|
||||||
|
|
||||||
|
filename = mazes[choice][0]
|
||||||
|
print(f"Загружен: {filename}")
|
||||||
|
return filename
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
builder = TextFileMazeBuilder()
|
||||||
|
|
||||||
|
filename = choose_maze()
|
||||||
|
maze = builder.build_from_file(f"mazes/{filename}")
|
||||||
|
|
||||||
|
view = ConsoleView()
|
||||||
|
view.update(f"Maze '{filename}' loaded")
|
||||||
|
|
||||||
|
strategies = {
|
||||||
|
"bfs": BFSStrategy(),
|
||||||
|
"dfs": DFSStrategy(),
|
||||||
|
"astar": AStarStrategy()
|
||||||
|
}
|
||||||
|
|
||||||
|
print("\nВыберите алгоритм:")
|
||||||
|
print(" bfs — поиск в ширину")
|
||||||
|
print(" dfs — поиск в глубину")
|
||||||
|
print(" astar — A*")
|
||||||
|
algo = input("Введите название: ").strip().lower()
|
||||||
|
|
||||||
|
strategy = strategies.get(algo, BFSStrategy())
|
||||||
|
|
||||||
|
solver = MazeSolver(maze, strategy)
|
||||||
|
stats = solver.solve()
|
||||||
|
print(stats)
|
||||||
|
|
||||||
|
path, visited = strategy.find_path(maze, maze.start, maze.exit)
|
||||||
|
view.render(maze, None, path)
|
||||||
|
|
||||||
|
player = Player(maze.start)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
cmd = input("Ход (w/a/s/d) или q для выхода: ").strip().lower()
|
||||||
|
if cmd == "q":
|
||||||
|
break
|
||||||
|
|
||||||
|
dxdy = {
|
||||||
|
"w": (0, -1),
|
||||||
|
"s": (0, 1),
|
||||||
|
"a": (-1, 0),
|
||||||
|
"d": (1, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cmd not in dxdy:
|
||||||
|
continue
|
||||||
|
|
||||||
|
dx, dy = dxdy[cmd]
|
||||||
|
new_cell = maze.get_cell(player.current_cell.x + dx,
|
||||||
|
player.current_cell.y + dy)
|
||||||
|
|
||||||
|
if not new_cell or not new_cell.is_passable():
|
||||||
|
print("Там стена, туда нельзя.")
|
||||||
|
continue
|
||||||
|
|
||||||
|
move = MoveCommand(player, new_cell)
|
||||||
|
move.execute()
|
||||||
|
view.render(maze, player.current_cell, path)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
19
BrychkinKA/src/model/cell.py
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
class Cell:
|
||||||
|
def __init__(self, x, y, is_wall=False, is_start=False, is_exit=False):
|
||||||
|
self.x = x
|
||||||
|
self.y = y
|
||||||
|
self.is_wall = is_wall
|
||||||
|
self.is_start = is_start
|
||||||
|
self.is_exit = is_exit
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"Cell({self.x},{self.y})"
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash((self.x, self.y))
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return isinstance(other, Cell) and self.x == other.x and self.y == other.y
|
||||||
|
|
||||||
|
def is_passable(self):
|
||||||
|
return not self.is_wall
|
||||||
23
BrychkinKA/src/model/maze.py
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
class Maze:
|
||||||
|
def __init__(self, width, height, cells, start, exit_):
|
||||||
|
self.width = width
|
||||||
|
self.height = height
|
||||||
|
self.cells = cells
|
||||||
|
self.start = start
|
||||||
|
self.exit = exit_
|
||||||
|
|
||||||
|
def get_cell(self, x, y):
|
||||||
|
return self.cells[y][x]
|
||||||
|
|
||||||
|
def get_neighbors(self, cell):
|
||||||
|
dirs = [(1,0), (-1,0), (0,1), (0,-1)]
|
||||||
|
result = []
|
||||||
|
|
||||||
|
for dx, dy in dirs:
|
||||||
|
nx, ny = cell.x + dx, cell.y + dy
|
||||||
|
if 0 <= nx < self.width and 0 <= ny < self.height:
|
||||||
|
n = self.get_cell(nx, ny)
|
||||||
|
if not n.is_wall:
|
||||||
|
result.append(n)
|
||||||
|
|
||||||
|
return result
|
||||||
32
BrychkinKA/src/solver/maze_solver.py
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
from src.solver.search_stats import SearchStats
|
||||||
|
|
||||||
|
class MazeSolver:
|
||||||
|
def __init__(self, maze, strategy):
|
||||||
|
self.maze = maze
|
||||||
|
self.strategy = strategy
|
||||||
|
|
||||||
|
def solve(self):
|
||||||
|
import time
|
||||||
|
t0 = time.perf_counter()
|
||||||
|
|
||||||
|
if self.maze.exit is None:
|
||||||
|
t1 = time.perf_counter()
|
||||||
|
return SearchStats(
|
||||||
|
time_ms=(t1 - t0) * 1000,
|
||||||
|
visited=0,
|
||||||
|
path_len=0
|
||||||
|
)
|
||||||
|
|
||||||
|
path, visited = self.strategy.find_path(
|
||||||
|
self.maze,
|
||||||
|
self.maze.start,
|
||||||
|
self.maze.exit
|
||||||
|
)
|
||||||
|
|
||||||
|
t1 = time.perf_counter()
|
||||||
|
|
||||||
|
return SearchStats(
|
||||||
|
time_ms=(t1 - t0) * 1000,
|
||||||
|
visited=len(visited),
|
||||||
|
path_len=len(path) if path else 0
|
||||||
|
)
|
||||||
8
BrychkinKA/src/solver/search_stats.py
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
class SearchStats:
|
||||||
|
def __init__(self, time_ms, visited, path_len):
|
||||||
|
self.time_ms = time_ms
|
||||||
|
self.visited = visited
|
||||||
|
self.path_len = path_len
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"SearchStats(time={self.time_ms:.2f}ms, visited={self.visited}, path={self.path_len})"
|
||||||
43
BrychkinKA/src/strategy/astar_strategy.py
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
import heapq
|
||||||
|
|
||||||
|
def manhattan(a, b):
|
||||||
|
return abs(a.x - b.x) + abs(a.y - b.y)
|
||||||
|
|
||||||
|
class AStarStrategy:
|
||||||
|
def find_path(self, maze, start, exit_):
|
||||||
|
g = {start: 0}
|
||||||
|
parent = {start: None}
|
||||||
|
|
||||||
|
counter = 0
|
||||||
|
open_heap = [(0, counter, start)]
|
||||||
|
in_open = {start}
|
||||||
|
visited = set()
|
||||||
|
|
||||||
|
while open_heap:
|
||||||
|
_, _, cur = heapq.heappop(open_heap)
|
||||||
|
in_open.discard(cur)
|
||||||
|
visited.add(cur)
|
||||||
|
|
||||||
|
if cur == exit_:
|
||||||
|
return self._reconstruct(parent, start, exit_), visited
|
||||||
|
|
||||||
|
for n in maze.get_neighbors(cur):
|
||||||
|
tentative = g[cur] + 1
|
||||||
|
if tentative < g.get(n, float('inf')):
|
||||||
|
g[n] = tentative
|
||||||
|
parent[n] = cur
|
||||||
|
f = tentative + manhattan(n, exit_)
|
||||||
|
if n not in in_open:
|
||||||
|
counter += 1
|
||||||
|
heapq.heappush(open_heap, (f, counter, n))
|
||||||
|
in_open.add(n)
|
||||||
|
|
||||||
|
return None, visited
|
||||||
|
|
||||||
|
def _reconstruct(self, parent, start, exit_):
|
||||||
|
path = []
|
||||||
|
cur = exit_
|
||||||
|
while cur:
|
||||||
|
path.append(cur)
|
||||||
|
cur = parent[cur]
|
||||||
|
return list(reversed(path))
|
||||||
29
BrychkinKA/src/strategy/bfs_strategy.py
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
from collections import deque
|
||||||
|
|
||||||
|
class BFSStrategy:
|
||||||
|
def find_path(self, maze, start, exit_):
|
||||||
|
queue = deque([start])
|
||||||
|
parent = {start: None}
|
||||||
|
visited = {start}
|
||||||
|
|
||||||
|
while queue:
|
||||||
|
cur = queue.popleft()
|
||||||
|
|
||||||
|
if cur == exit_:
|
||||||
|
return self._reconstruct(parent, start, exit_), visited
|
||||||
|
|
||||||
|
for n in maze.get_neighbors(cur):
|
||||||
|
if n not in visited:
|
||||||
|
visited.add(n)
|
||||||
|
parent[n] = cur
|
||||||
|
queue.append(n)
|
||||||
|
|
||||||
|
return None, visited
|
||||||
|
|
||||||
|
def _reconstruct(self, parent, start, exit_):
|
||||||
|
path = []
|
||||||
|
cur = exit_
|
||||||
|
while cur:
|
||||||
|
path.append(cur)
|
||||||
|
cur = parent[cur]
|
||||||
|
return list(reversed(path))
|
||||||
27
BrychkinKA/src/strategy/dfs_strategy.py
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
class DFSStrategy:
|
||||||
|
def find_path(self, maze, start, exit_):
|
||||||
|
stack = [start]
|
||||||
|
parent = {start: None}
|
||||||
|
visited = {start}
|
||||||
|
|
||||||
|
while stack:
|
||||||
|
cur = stack.pop()
|
||||||
|
|
||||||
|
if cur == exit_:
|
||||||
|
return self._reconstruct(parent, start, exit_), visited
|
||||||
|
|
||||||
|
for n in maze.get_neighbors(cur):
|
||||||
|
if n not in visited:
|
||||||
|
visited.add(n)
|
||||||
|
parent[n] = cur
|
||||||
|
stack.append(n)
|
||||||
|
|
||||||
|
return None, visited
|
||||||
|
|
||||||
|
def _reconstruct(self, parent, start, exit_):
|
||||||
|
path = []
|
||||||
|
cur = exit_
|
||||||
|
while cur:
|
||||||
|
path.append(cur)
|
||||||
|
cur = parent[cur]
|
||||||
|
return list(reversed(path))
|
||||||
9
BrychkinKA/src/strategy/path_finding_strategy.py
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from typing import List
|
||||||
|
from src.model.cell import Cell
|
||||||
|
from src.model.maze import Maze
|
||||||
|
|
||||||
|
class PathFindingStrategy(ABC):
|
||||||
|
@abstractmethod
|
||||||
|
def find_path(self, maze: Maze, start: Cell, exit_: Cell) -> List[Cell]:
|
||||||
|
pass
|
||||||
10
BrychkinKA/src/ui/command.py
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
|
||||||
|
class Command(ABC):
|
||||||
|
@abstractmethod
|
||||||
|
def execute(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def undo(self):
|
||||||
|
pass
|
||||||
33
BrychkinKA/src/ui/console_view.py
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
import os
|
||||||
|
from typing import List
|
||||||
|
from src.model.cell import Cell
|
||||||
|
from src.model.maze import Maze
|
||||||
|
from .observer import Observer
|
||||||
|
|
||||||
|
class ConsoleView(Observer):
|
||||||
|
def update(self, event: str):
|
||||||
|
print(f"[EVENT] {event}")
|
||||||
|
|
||||||
|
def render(self, maze: Maze, player_pos: Cell = None, path: List[Cell] = None):
|
||||||
|
os.system('cls' if os.name == 'nt' else 'clear')
|
||||||
|
|
||||||
|
path_set = set(path) if path else set()
|
||||||
|
|
||||||
|
for y in range(maze.height):
|
||||||
|
row = ""
|
||||||
|
for x in range(maze.width):
|
||||||
|
cell = maze.get_cell(x, y)
|
||||||
|
|
||||||
|
if cell.is_wall:
|
||||||
|
row += "#"
|
||||||
|
elif cell.is_start:
|
||||||
|
row += "S"
|
||||||
|
elif cell.is_exit:
|
||||||
|
row += "E"
|
||||||
|
elif player_pos and cell.x == player_pos.x and cell.y == player_pos.y:
|
||||||
|
row += "@"
|
||||||
|
elif cell in path_set:
|
||||||
|
row += "*"
|
||||||
|
else:
|
||||||
|
row += " "
|
||||||
|
print(row)
|
||||||
17
BrychkinKA/src/ui/move_command.py
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
from src.model.cell import Cell
|
||||||
|
from .command import Command
|
||||||
|
from .player import Player
|
||||||
|
|
||||||
|
class MoveCommand(Command):
|
||||||
|
def __init__(self, player: Player, new_cell: Cell):
|
||||||
|
self.player = player
|
||||||
|
self.new_cell = new_cell
|
||||||
|
self.prev_cell = None
|
||||||
|
|
||||||
|
def execute(self):
|
||||||
|
self.prev_cell = self.player.current_cell
|
||||||
|
self.player.move_to(self.new_cell)
|
||||||
|
|
||||||
|
def undo(self):
|
||||||
|
if self.prev_cell:
|
||||||
|
self.player.move_to(self.prev_cell)
|
||||||