diff --git a/Esami/2022-2023/Esame-7/ex2/.DS_Store b/Esami/2022-2023/Esame-7/ex2/.DS_Store new file mode 100644 index 0000000..7e70de8 Binary files /dev/null and b/Esami/2022-2023/Esame-7/ex2/.DS_Store differ diff --git a/Esami/2022-2023/Esame-7/ex2/A/.DS_Store b/Esami/2022-2023/Esame-7/ex2/A/.DS_Store new file mode 100644 index 0000000..d3cfc87 Binary files /dev/null and b/Esami/2022-2023/Esame-7/ex2/A/.DS_Store differ diff --git a/Esami/2022-2023/Esame-7/ex2/A/B/b.txt b/Esami/2022-2023/Esame-7/ex2/A/B/b.txt new file mode 100644 index 0000000..ec7dd5e --- /dev/null +++ b/Esami/2022-2023/Esame-7/ex2/A/B/b.txt @@ -0,0 +1,6 @@ +cat dog + + + bird + + diff --git a/Esami/2022-2023/Esame-7/ex2/A/C/b.txt b/Esami/2022-2023/Esame-7/ex2/A/C/b.txt new file mode 100644 index 0000000..6e00b79 --- /dev/null +++ b/Esami/2022-2023/Esame-7/ex2/A/C/b.txt @@ -0,0 +1,9 @@ + fish bird + + monkey + + + lizard + + + diff --git a/Esami/2022-2023/Esame-7/ex2/A/C/c.txt b/Esami/2022-2023/Esame-7/ex2/A/C/c.txt new file mode 100644 index 0000000..e9187c7 --- /dev/null +++ b/Esami/2022-2023/Esame-7/ex2/A/C/c.txt @@ -0,0 +1,17 @@ + + + + + + + + + turtle + + +cat dog + + + + + zebra \ No newline at end of file diff --git a/Esami/2022-2023/Esame-7/ex2/A/D/a.txt b/Esami/2022-2023/Esame-7/ex2/A/D/a.txt new file mode 100644 index 0000000..b9cea0d --- /dev/null +++ b/Esami/2022-2023/Esame-7/ex2/A/D/a.txt @@ -0,0 +1,9 @@ +gnu + + + + cat dog + + + + diff --git a/Esami/2022-2023/Esame-7/ex2/A/D/b.txt b/Esami/2022-2023/Esame-7/ex2/A/D/b.txt new file mode 100644 index 0000000..86e7fd8 --- /dev/null +++ b/Esami/2022-2023/Esame-7/ex2/A/D/b.txt @@ -0,0 +1,15 @@ + + + + +dog + + + + + + +turtle + + + fish \ No newline at end of file diff --git a/Esami/2022-2023/Esame-7/ex2/A/a.txt b/Esami/2022-2023/Esame-7/ex2/A/a.txt new file mode 100644 index 0000000..86180a1 --- /dev/null +++ b/Esami/2022-2023/Esame-7/ex2/A/a.txt @@ -0,0 +1,18 @@ + + + + + + bird + + + + + + + turtle + + + + +fish \ No newline at end of file diff --git a/Esami/2022-2023/Esame-7/ex2/B/.DS_Store b/Esami/2022-2023/Esame-7/ex2/B/.DS_Store new file mode 100644 index 0000000..5f0cb0c Binary files /dev/null and b/Esami/2022-2023/Esame-7/ex2/B/.DS_Store differ diff --git a/Esami/2022-2023/Esame-7/ex2/B/B/b.txt b/Esami/2022-2023/Esame-7/ex2/B/B/b.txt new file mode 100644 index 0000000..130a043 --- /dev/null +++ b/Esami/2022-2023/Esame-7/ex2/B/B/b.txt @@ -0,0 +1,5 @@ + + + + +dog \ No newline at end of file diff --git a/Esami/2022-2023/Esame-7/ex2/B/C/b.txt b/Esami/2022-2023/Esame-7/ex2/B/C/b.txt new file mode 100644 index 0000000..f557974 --- /dev/null +++ b/Esami/2022-2023/Esame-7/ex2/B/C/b.txt @@ -0,0 +1 @@ +cat dog mouse \ No newline at end of file diff --git a/Esami/2022-2023/Esame-7/ex2/B/C/c.tst b/Esami/2022-2023/Esame-7/ex2/B/C/c.tst new file mode 100644 index 0000000..3f8e2b2 --- /dev/null +++ b/Esami/2022-2023/Esame-7/ex2/B/C/c.tst @@ -0,0 +1,21 @@ +turtle + + + + + +cat + +dog + + +fish + + +zebra + +bird + + + +gnu \ No newline at end of file diff --git a/Esami/2022-2023/Esame-7/ex2/B/D/a.txt b/Esami/2022-2023/Esame-7/ex2/B/D/a.txt new file mode 100644 index 0000000..d6e1b72 --- /dev/null +++ b/Esami/2022-2023/Esame-7/ex2/B/D/a.txt @@ -0,0 +1,5 @@ + + + + +dog zebra bird \ No newline at end of file diff --git a/Esami/2022-2023/Esame-7/ex2/B/D/b.txt b/Esami/2022-2023/Esame-7/ex2/B/D/b.txt new file mode 100644 index 0000000..ddaeb89 --- /dev/null +++ b/Esami/2022-2023/Esame-7/ex2/B/D/b.txt @@ -0,0 +1,7 @@ + fish + + + + + + lizard \ No newline at end of file diff --git a/Esami/2022-2023/Esame-7/ex2/B/a.txt b/Esami/2022-2023/Esame-7/ex2/B/a.txt new file mode 100644 index 0000000..7793854 --- /dev/null +++ b/Esami/2022-2023/Esame-7/ex2/B/a.txt @@ -0,0 +1,16 @@ + + + + + tuna + + + + + + whale + + + + + octopus \ No newline at end of file diff --git a/Esami/2022-2023/Esame-7/ex2/C/.DS_Store b/Esami/2022-2023/Esame-7/ex2/C/.DS_Store new file mode 100644 index 0000000..0183dea Binary files /dev/null and b/Esami/2022-2023/Esame-7/ex2/C/.DS_Store differ diff --git a/Esami/2022-2023/Esame-7/ex2/C/B/a.txt b/Esami/2022-2023/Esame-7/ex2/C/B/a.txt new file mode 100644 index 0000000..7e4f910 --- /dev/null +++ b/Esami/2022-2023/Esame-7/ex2/C/B/a.txt @@ -0,0 +1,11 @@ + + + + + +dog + + + + + diff --git a/Esami/2022-2023/Esame-7/ex2/C/B/b.txt b/Esami/2022-2023/Esame-7/ex2/C/B/b.txt new file mode 100644 index 0000000..80c4524 --- /dev/null +++ b/Esami/2022-2023/Esame-7/ex2/C/B/b.txt @@ -0,0 +1,9 @@ + + + + + kitten + + + + bird \ No newline at end of file diff --git a/Esami/2022-2023/Esame-7/ex2/C/C/a.txt b/Esami/2022-2023/Esame-7/ex2/C/C/a.txt new file mode 100644 index 0000000..7a0e46b --- /dev/null +++ b/Esami/2022-2023/Esame-7/ex2/C/C/a.txt @@ -0,0 +1,10 @@ + + + + turtle + + + + + + dog \ No newline at end of file diff --git a/Esami/2022-2023/Esame-7/ex2/C/C/b.txt b/Esami/2022-2023/Esame-7/ex2/C/C/b.txt new file mode 100644 index 0000000..28159f3 --- /dev/null +++ b/Esami/2022-2023/Esame-7/ex2/C/C/b.txt @@ -0,0 +1,10 @@ + + + + + + + + + +tuna \ No newline at end of file diff --git a/Esami/2022-2023/Esame-7/ex2/C/C/c.txt b/Esami/2022-2023/Esame-7/ex2/C/C/c.txt new file mode 100644 index 0000000..cad133c --- /dev/null +++ b/Esami/2022-2023/Esame-7/ex2/C/C/c.txt @@ -0,0 +1,9 @@ + zebra +bird + + + + + + + diff --git a/Esami/2022-2023/Esame-7/ex2/C/D/E/a.txt b/Esami/2022-2023/Esame-7/ex2/C/D/E/a.txt new file mode 100644 index 0000000..75735db --- /dev/null +++ b/Esami/2022-2023/Esame-7/ex2/C/D/E/a.txt @@ -0,0 +1 @@ +cat dog bird \ No newline at end of file diff --git a/Esami/2022-2023/Esame-7/ex2/C/D/a.txt b/Esami/2022-2023/Esame-7/ex2/C/D/a.txt new file mode 100644 index 0000000..9e5f5f8 --- /dev/null +++ b/Esami/2022-2023/Esame-7/ex2/C/D/a.txt @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Esami/2022-2023/Esame-7/ex2/C/D/b.txt b/Esami/2022-2023/Esame-7/ex2/C/D/b.txt new file mode 100644 index 0000000..2c546b6 --- /dev/null +++ b/Esami/2022-2023/Esame-7/ex2/C/D/b.txt @@ -0,0 +1 @@ +turtle zebra \ No newline at end of file diff --git a/Esami/2022-2023/Esame-7/ex2/C/a.txt b/Esami/2022-2023/Esame-7/ex2/C/a.txt new file mode 100644 index 0000000..8c35c5a --- /dev/null +++ b/Esami/2022-2023/Esame-7/ex2/C/a.txt @@ -0,0 +1,8 @@ + + + + + + + + bird \ No newline at end of file diff --git a/Esami/2022-2023/Esame-7/func3/expected_1.txt b/Esami/2022-2023/Esame-7/func3/expected_1.txt new file mode 100644 index 0000000..29bae3d --- /dev/null +++ b/Esami/2022-2023/Esame-7/func3/expected_1.txt @@ -0,0 +1 @@ +-3227, +322.7, -141, -23.5, 17 diff --git a/Esami/2022-2023/Esame-7/func3/expected_2.txt b/Esami/2022-2023/Esame-7/func3/expected_2.txt new file mode 100644 index 0000000..32142ef --- /dev/null +++ b/Esami/2022-2023/Esame-7/func3/expected_2.txt @@ -0,0 +1 @@ +-88824.39910, 20.87665, 1804.721, -715.167, 6811.6, 46259, -45, +31, +40, +82, -7 diff --git a/Esami/2022-2023/Esame-7/func3/expected_3.txt b/Esami/2022-2023/Esame-7/func3/expected_3.txt new file mode 100644 index 0000000..629882f --- /dev/null +++ b/Esami/2022-2023/Esame-7/func3/expected_3.txt @@ -0,0 +1 @@ ++4932352.6486360, -523093.1581162, +820400.2176650, 891873.4710866, +928639.2560113, +33451.7913317, 778431.257401, +1707305.32292, +1096.548757, 279559.2779, 4587.19791, -16031.505, -46.799850, +10.27237, 14.61985, 8054.325, 198551.4, -8.54341, 4.28243, 8970.38, -412.43, 11759, +12436, -5.725, 2.844, 4.781, +257.0, -671, -184, -1.23, 2.12, 2.47, +2.48, 613, -99, +9.2, 45 diff --git a/Esami/2022-2023/Esame-7/func3/expected_4.txt b/Esami/2022-2023/Esame-7/func3/expected_4.txt new file mode 100644 index 0000000..7ac2d7b --- /dev/null +++ b/Esami/2022-2023/Esame-7/func3/expected_4.txt @@ -0,0 +1 @@ +-903571.26500812, 781178.53254829, 29052450.257403, +69908.90553807, 634222.5469958, +4349491.921123, 52417532.92652, -1573.66817318, +8248286.34806, 14563566.9842, -7102.1503594, +1523803.1813, -814710.2255, 86.50008902, 761997.1274, +5529482.592, 69868854.39, -914527.226, -77.3999301, -1.31144051, -0.79610605, 5.83560498, +50.2403216, 790734.220, 2236870.50, -68903177, -10.554814, -9.2406357, +486.24293, +2573654.6, +4040879.3, +5992.445, 982186.8, +7693989, -100.609, +5.30920, +19.7162, +473.640, +481.560, +611.196, +6681.10, -41.654, 9702.6, 89670, -72.10, +11.10, 73.77, +179.8, +100, -1.9, 59 diff --git a/Esami/2022-2023/Esame-7/func3/in_1.txt b/Esami/2022-2023/Esame-7/func3/in_1.txt new file mode 100644 index 0000000..a090df4 --- /dev/null +++ b/Esami/2022-2023/Esame-7/func3/in_1.txt @@ -0,0 +1 @@ +-23.5 17 -141 +322.7 -3227 \ No newline at end of file diff --git a/Esami/2022-2023/Esame-7/func3/in_2.txt b/Esami/2022-2023/Esame-7/func3/in_2.txt new file mode 100644 index 0000000..8f8f372 --- /dev/null +++ b/Esami/2022-2023/Esame-7/func3/in_2.txt @@ -0,0 +1 @@ +1804.721 +40 -715.167 -7 46259 +31 -45 6811.6 -88824.39910 20.87665 +82 diff --git a/Esami/2022-2023/Esame-7/func3/in_3.txt b/Esami/2022-2023/Esame-7/func3/in_3.txt new file mode 100644 index 0000000..45e2f0b --- /dev/null +++ b/Esami/2022-2023/Esame-7/func3/in_3.txt @@ -0,0 +1 @@ ++9.2 11759 +33451.7913317 +10.27237 778431.257401 +1707305.32292 +257.0 -523093.1581162 2.47 198551.4 -99 +820400.2176650 -671 279559.2779 891873.4710866 +928639.2560113 -1.23 4.28243 -46.799850 +12436 45 2.844 -16031.505 +2.48 -5.725 4587.19791 4.781 2.12 -184 8970.38 -8.54341 +4932352.6486360 +1096.548757 -412.43 14.61985 613 8054.325 diff --git a/Esami/2022-2023/Esame-7/func3/in_4.txt b/Esami/2022-2023/Esame-7/func3/in_4.txt new file mode 100644 index 0000000..7040893 --- /dev/null +++ b/Esami/2022-2023/Esame-7/func3/in_4.txt @@ -0,0 +1 @@ +-1573.66817318 +473.640 -903571.26500812 +5.30920 790734.220 -0.79610605 +8248286.34806 634222.5469958 73.77 +5529482.592 +4349491.921123 29052450.257403 -100.609 +4040879.3 -68903177 -914527.226 +481.560 +19.7162 +11.10 -1.9 5.83560498 9702.6 -10.554814 761997.1274 +6681.10 +179.8 -7102.1503594 -41.654 -814710.2255 +50.2403216 -77.3999301 +486.24293 +1523803.1813 59 +2573654.6 982186.8 +5992.445 +100 2236870.50 +7693989 781178.53254829 86.50008902 52417532.92652 14563566.9842 69868854.39 -1.31144051 -72.10 +69908.90553807 +611.196 -9.2406357 89670 diff --git a/Esami/2022-2023/Esame-7/func4/in_1.txt b/Esami/2022-2023/Esame-7/func4/in_1.txt new file mode 100644 index 0000000..d58bf93 --- /dev/null +++ b/Esami/2022-2023/Esame-7/func4/in_1.txt @@ -0,0 +1,3 @@ +1 2 3 4 +5 6 7 8 +9 10 11 12 \ No newline at end of file diff --git a/Esami/2022-2023/Esame-7/func4/in_2.txt b/Esami/2022-2023/Esame-7/func4/in_2.txt new file mode 100644 index 0000000..0f96fef --- /dev/null +++ b/Esami/2022-2023/Esame-7/func4/in_2.txt @@ -0,0 +1,6 @@ +496 -893 -609 +247 -954 -278 +881 -660 589 +-391 271 473 +-884 699 273 +626 795 -694 diff --git a/Esami/2022-2023/Esame-7/func4/in_3.txt b/Esami/2022-2023/Esame-7/func4/in_3.txt new file mode 100644 index 0000000..72c781f --- /dev/null +++ b/Esami/2022-2023/Esame-7/func4/in_3.txt @@ -0,0 +1,7 @@ +246 -822 -311 -102 431 757 -289 -288 -211 -791 -897 -939 892 -309 618 31 -555 116 226 -332 +-22 866 -735 -909 175 235 -798 102 920 194 -451 -243 -274 -230 401 -206 -416 -573 389 -332 +-428 369 709 717 661 578 -79 529 -67 476 760 957 -90 125 114 172 163 -420 531 -450 +768 -988 -177 442 862 -671 468 -382 494 -683 904 856 -373 -416 -715 -869 -708 780 386 526 +-403 236 824 -770 684 -811 -457 768 -526 76 -894 274 -161 -490 -830 181 -472 912 962 -250 +-636 38 -501 706 -945 -694 132 -872 -661 -298 317 229 587 452 -168 949 804 -690 -84 -197 +-40 -370 -247 194 486 -170 -526 191 -290 862 537 678 -911 719 570 -808 407 30 398 -104 diff --git a/Esami/2022-2023/Esame-7/func4/in_4.txt b/Esami/2022-2023/Esame-7/func4/in_4.txt new file mode 100644 index 0000000..4a5eac9 --- /dev/null +++ b/Esami/2022-2023/Esame-7/func4/in_4.txt @@ -0,0 +1,13 @@ +197 -394 -810 819 812 -126 -702 -581 -89 -830 911 77 468 431 448 -779 528 -442 61 -471 237 488 779 +-805 840 -870 -386 -211 360 -49 326 -466 -2 -297 -251 954 507 -815 166 175 696 783 596 -183 -675 -782 +-504 -24 -563 -529 -765 598 262 233 -744 251 83 90 274 -491 748 462 -206 87 114 700 -287 -543 -487 +-320 940 -162 322 595 812 -448 -806 13 835 511 -129 -441 -710 549 -328 235 -939 693 -879 -810 -116 -639 +257 867 94 -860 -25 470 -893 -26 -1 -968 -751 -299 -405 -907 -321 -54 -198 285 -812 861 587 51 475 +-116 -106 195 763 25 409 -9 -961 47 -5 -884 -441 996 919 -632 301 -734 71 -803 -505 578 -791 -384 +190 882 777 -967 315 72 841 -150 818 -882 239 -119 916 13 825 774 -545 332 634 227 -480 -378 261 +-953 -935 157 346 -865 664 -112 867 726 -121 883 107 55 433 11 -682 32 -591 404 -598 648 817 -840 +-588 311 -258 -651 -189 -557 -418 -127 978 564 654 -354 681 730 -585 353 873 804 215 -903 608 -254 126 +-282 -555 504 987 476 451 -9 -941 67 28 72 -119 341 941 293 -553 500 657 587 -457 287 637 -522 +-786 312 453 569 -156 -696 -88 370 -63 855 -795 88 621 520 152 -746 -242 -262 47 917 -193 353 -812 +304 -301 -510 976 369 -587 183 -489 -32 -157 -856 22 437 698 205 842 -921 -60 -434 259 -987 615 685 +-209 286 252 -381 417 -875 562 676 55 850 -936 -204 233 603 -525 988 756 332 716 686 295 13 14 diff --git a/Esami/2022-2023/Esame-7/func5/expected_1.png b/Esami/2022-2023/Esame-7/func5/expected_1.png new file mode 100644 index 0000000..f8ead5f Binary files /dev/null and b/Esami/2022-2023/Esame-7/func5/expected_1.png differ diff --git a/Esami/2022-2023/Esame-7/func5/expected_2.png b/Esami/2022-2023/Esame-7/func5/expected_2.png new file mode 100644 index 0000000..25732a5 Binary files /dev/null and b/Esami/2022-2023/Esame-7/func5/expected_2.png differ diff --git a/Esami/2022-2023/Esame-7/func5/expected_3.png b/Esami/2022-2023/Esame-7/func5/expected_3.png new file mode 100644 index 0000000..bc001e8 Binary files /dev/null and b/Esami/2022-2023/Esame-7/func5/expected_3.png differ diff --git a/Esami/2022-2023/Esame-7/func5/expected_4.png b/Esami/2022-2023/Esame-7/func5/expected_4.png new file mode 100644 index 0000000..d6dc182 Binary files /dev/null and b/Esami/2022-2023/Esame-7/func5/expected_4.png differ diff --git a/Esami/2022-2023/Esame-7/func5/in_1.txt b/Esami/2022-2023/Esame-7/func5/in_1.txt new file mode 100644 index 0000000..19738ca --- /dev/null +++ b/Esami/2022-2023/Esame-7/func5/in_1.txt @@ -0,0 +1,3 @@ +diagonalDOWN 0 255 0 -30 -40 110 +diagonalUP 255 0 0 20 100 200 +diagonalUP 0 0 255 10 120 50 \ No newline at end of file diff --git a/Esami/2022-2023/Esame-7/func5/in_2.txt b/Esami/2022-2023/Esame-7/func5/in_2.txt new file mode 100644 index 0000000..b80f1c3 --- /dev/null +++ b/Esami/2022-2023/Esame-7/func5/in_2.txt @@ -0,0 +1,11 @@ +diagonalDOWN 207 243 215 133 240 166 +diagonalUP 204 218 166 165 48 246 +diagonalUP 98 197 246 39 198 172 +diagonalDOWN 35 223 76 175 115 259 +diagonalDOWN 234 213 195 69 251 215 +diagonalDOWN 86 154 219 -26 203 243 +diagonalDOWN 91 77 234 274 207 145 +diagonalDOWN 243 127 216 152 52 109 +diagonalUP 14 213 250 66 35 236 +diagonalDOWN 138 174 26 87 7 261 +diagonalUP 67 57 27 130 278 273 diff --git a/Esami/2022-2023/Esame-7/func5/in_3.txt b/Esami/2022-2023/Esame-7/func5/in_3.txt new file mode 100644 index 0000000..43cab57 --- /dev/null +++ b/Esami/2022-2023/Esame-7/func5/in_3.txt @@ -0,0 +1,21 @@ +diagonalUP 140 236 254 16 410 414 +diagonalUP 9 172 61 197 66 399 +diagonalDOWN 66 210 138 288 -1 491 +diagonalDOWN 175 133 137 338 -13 289 +diagonalUP 112 151 95 285 36 201 +diagonalDOWN 189 238 160 -40 55 243 +diagonalUP 204 11 44 75 167 470 +diagonalDOWN 66 190 233 -14 31 336 +diagonalUP 2 63 64 -9 247 309 +diagonalUP 29 204 187 347 191 286 +diagonalDOWN 206 78 16 270 47 496 +diagonalDOWN 160 53 17 220 -50 376 +diagonalDOWN 41 116 189 152 430 128 +diagonalUP 184 177 96 47 379 487 +diagonalDOWN 86 26 187 27 94 102 +diagonalUP 149 238 185 341 452 397 +diagonalDOWN 245 214 186 327 310 115 +diagonalDOWN 254 111 57 327 119 373 +diagonalDOWN 224 92 55 169 97 208 +diagonalDOWN 4 244 216 153 126 386 +diagonalDOWN 59 86 186 224 28 348 diff --git a/Esami/2022-2023/Esame-7/func5/in_4.txt b/Esami/2022-2023/Esame-7/func5/in_4.txt new file mode 100644 index 0000000..5c3a561 --- /dev/null +++ b/Esami/2022-2023/Esame-7/func5/in_4.txt @@ -0,0 +1,55 @@ +diagonalDOWN 43 77 146 373 79 265 +diagonalUP 234 235 100 478 365 311 +diagonalDOWN 98 198 5 163 386 195 +diagonalDOWN 176 75 33 231 366 156 +diagonalUP 161 166 234 82 110 389 +diagonalUP 146 68 249 160 379 126 +diagonalDOWN 55 8 20 295 249 493 +diagonalDOWN 74 196 173 16 61 245 +diagonalDOWN 199 125 1 351 431 133 +diagonalDOWN 124 134 37 334 441 458 +diagonalUP 10 238 106 359 226 215 +diagonalDOWN 240 21 24 148 384 136 +diagonalDOWN 25 31 115 53 58 164 +diagonalUP 14 234 239 256 202 335 +diagonalUP 5 139 64 472 107 475 +diagonalUP 78 59 31 149 487 432 +diagonalUP 122 196 153 30 304 451 +diagonalUP 82 133 39 34 307 438 +diagonalUP 103 94 60 235 281 270 +diagonalUP 204 26 133 441 280 339 +diagonalUP 150 103 169 370 27 144 +diagonalUP 183 10 100 480 77 233 +diagonalDOWN 116 230 252 432 112 107 +diagonalUP 64 63 203 161 86 340 +diagonalUP 227 19 53 307 134 163 +diagonalDOWN 62 118 219 491 69 186 +diagonalDOWN 50 70 25 444 455 328 +diagonalUP 94 152 53 230 125 100 +diagonalUP 222 148 18 349 463 490 +diagonalDOWN 71 71 208 383 213 192 +diagonalDOWN 37 207 14 142 167 272 +diagonalDOWN 62 51 183 -24 172 199 +diagonalUP 0 230 207 87 307 463 +diagonalDOWN 99 91 153 72 322 425 +diagonalUP 181 165 185 -10 482 399 +diagonalDOWN 53 192 23 398 413 142 +diagonalDOWN 84 220 98 40 268 486 +diagonalDOWN 27 71 11 170 68 399 +diagonalDOWN 203 54 152 169 210 173 +diagonalUP 76 113 202 138 244 238 +diagonalDOWN 151 122 238 322 468 315 +diagonalUP 108 120 238 496 358 395 +diagonalUP 123 112 10 50 447 310 +diagonalUP 43 102 99 205 382 272 +diagonalUP 235 124 68 334 477 210 +diagonalDOWN 30 116 171 232 231 247 +diagonalUP 139 135 12 284 -2 240 +diagonalUP 59 5 243 475 442 471 +diagonalUP 8 38 122 244 245 277 +diagonalUP 114 128 143 264 313 423 +diagonalUP 214 236 213 402 346 421 +diagonalUP 238 89 108 294 212 220 +diagonalUP 8 250 174 359 37 204 +diagonalUP 108 123 124 247 340 256 +diagonalDOWN 51 76 45 405 -5 168 diff --git a/Esami/2022-2023/Esame-7/grade.py b/Esami/2022-2023/Esame-7/grade.py new file mode 100644 index 0000000..5a2c765 --- /dev/null +++ b/Esami/2022-2023/Esame-7/grade.py @@ -0,0 +1,559 @@ +# -*- coding: utf-8 -*- +import testlib +import isrecursive +import os +import sys +import nary_tree +from testlib import my_print, COL + + +############ check that you have renamed the file as program.py ########### +if not os.path.isfile('program.py'): + print( 'WARNING: Save program.empty.py as program.py\n' + 'ATTENZIONE: salvare program.vuoto.py con nome program.py') + sys.exit(0) +############################################################################# + +import program + +############################################################################# +#### Use DEBUG=True to disable the recursion tests and enable the +#### stack trace: each error will produce a more verbose output +#### +#### Mettete DEBUG=True per disattivare i test di ricorsione e +#### fare debug delle funzioni più facilmente attivando stack trace +#DEBUG = True +DEBUG = False +############################################################################# + +################################################################################ +# ------- THE SOURCE CODE FROM THIS POINT FORWARD IS FOR TESTING ONLY -------- # +# ----- The use of the following functions in your program is forbidden ------ # +# ---------------------------------------------------------------------------- # +# --- IL CODICE SORGENTE DI SEGUITO È ESCLUSIVAMENTE PER EFFETTUARE I TEST --- # +# ------- L'uso delle funzioni seguenti nel vostro programma è vietato --------# +################################################################################ + +def test_personal_data_entry(): + non_filled = { + 'nome': ['nome', 'cognome', 'matricola' ], + 'name': ['name', 'surname', 'student_id'] + } + for tag, vars in non_filled.items(): + if tag in program.__dict__: + for v in vars: + if getattr(program, v, v.upper()) == v.upper(): + print( + f"{COL['RED']}ERROR: Please assign the '{v}' variable with YOUR {v.upper()} in program.py{COL['RST']}") + return -32 # se una var manca + return 0 # se per una lingua tutto OK + return -32 # se nessuna lingua OK + +# ----------------------------------- FUNC. 1 ----------------------------------- # + +def do_func1_tests(diz1, diz2, expected): + res = program.func1(diz1, diz2) + if res != expected: + my_print(f'''{'*'*50}\n[ERROR] Il dizionario ritornato è sbagliato! / The returned dict is incorrect!\nReturned={res}, expected={expected}.\n{'*'*50}''') + return 0 + return 0.5 + + +def test_func1_1(): + ''' + diz1 = { 1: ['a', 'bc', 'a'], 2: ['b', 'cr', 'e'], 3: ['a', 'qrt', 'st'] } + diz2 = { 1: ['a', 'cd', 'f'], 5: ['b', 'cr', 'e'], 3: ['a', 'bn', 'c'] } + expected = {1: ['bc', 'cd', 'f'], 3: ['qrt', 'bn', 'st', 'c']} + ''' + diz1 = { 1: ['a', 'bc', 'a'], 2: ['b', 'cr', 'e'], 3: ['a', 'qrt', 'st'] } + diz2 = { 1: ['a', 'cd', 'f'], 5: ['b', 'cr', 'e'], 3: ['a', 'bn', 'c'] } + expected = {1: ['bc', 'cd', 'f'], 3: ['qrt', 'bn', 'st', 'c']} + return do_func1_tests(diz1, diz2, expected) + +def test_func1_2(): + ''' + diz1 = { 1: ['a', 'bc', 'a'], 2: ['b', 'cr', 'e'], 3: ['a', 'qrt', 'st'] } + diz2 = { 1: ['a', 'cd', 'f'], 2: ['b', 'cr', 'e'], 8: ['a', 'bn', 'c'] } + expected = {1: ['bc', 'cd', 'f'], 2: []} + ''' + diz1 = { 1: ['a', 'bc', 'a'], 2: ['b', 'cr', 'e'], 3: ['a', 'qrt', 'st'] } + diz2 = { 1: ['a', 'cd', 'f'], 2: ['b', 'cr', 'e'], 8: ['a', 'bn', 'c'] } + expected = {1: ['bc', 'cd', 'f'], 2: []} + return do_func1_tests(diz1, diz2, expected) + +def test_func1_3(): + ''' + diz1 = { 1: ['a', 'bc', 'a'], 2: ['b', 'cr', 'e'], 3: ['a', 'qrt', 'st'] } + diz2 = { 4: ['a', 'cd', 'f'], 5: ['b', 'cr', 'e'], 6: ['a', 'bn', 'c'] } + expected = {} + ''' + diz1 = { 1: ['a', 'bc', 'a'], 2: ['b', 'cr', 'e'], 3: ['a', 'qrt', 'st'] } + diz2 = { 4: ['a', 'cd', 'f'], 5: ['b', 'cr', 'e'], 6: ['a', 'bn', 'c'] } + expected = {} + return do_func1_tests(diz1, diz2, expected) + +def test_func1_4(): + ''' + diz1 = { 1: ['a', 'bc', 'a', 'sei', 'nove', 'q' ], 2: ['b', 'cr', 'e'], 3: ['a', 'qrt', 'st'] } + diz2 = { 1: ['a', 'cd', 'f', 'be', 'tre', 'otto'], -1: ['b', 'cr', 'e'], 6: ['a', 'bn', 'c'] } + expected = {1: ['nove', 'otto', 'sei', 'tre', 'bc', 'be', 'cd', 'f', 'q']} + ''' + diz1 = { 1: ['a', 'bc', 'a', 'sei', 'nove', 'q' ], 2: ['b', 'cr', 'e'], 3: ['a', 'qrt', 'st'] } + diz2 = { 1: ['a', 'cd', 'f', 'be', 'tre', 'otto'], -1: ['b', 'cr', 'e'], 6: ['a', 'bn', 'c'] } + expected = {1: ['nove', 'otto', 'sei', 'tre', 'bc', 'be', 'cd', 'f', 'q']} + return do_func1_tests(diz1, diz2, expected) + +# ----------------------------------- FUNC. 2 ----------------------------------- # + +def do_func2_tests(text, expected): + res = program.func2(text) + + if res != expected: + my_print(f'''{'*'*50}\n[ERROR] Il dizionario ritornato è sbagliato! / The returned dictionary is incorrect!\nReturned={res}, expected={expected}.\n{'*'*50}''') + if type(res) == dict: + for k in expected: + if expected[k] != res[k]: + my_print(f'''[ERROR] Ad esempio, il carattere {k} si trova in {expected[k]} parole invece che in {res[k]}.''') + my_print(f'''[ERROR] For example, char {k} appears in {expected[k]} words instead than in {res[k]}.''') + break + return 0 + return 0.5 + + +def test_func2_1(): + ''' + testo = 'sOtto lA panca La caPra Canta Sopra LA Panca La CaPra crepa' + expected = { 's':2, 'l':4, 'p':6, 'c':6} + ''' + text = 'sOtto lA panca La caPra Canta Sopra LA Panca La CaPra crepa' + expected = {'s': 2, 'l': 4, 'p': 6, 'c': 6} + return do_func2_tests(text, expected) + +def test_func2_2(): + ''' + text = 'Nel Mezzo del caMmin Di nostra vita mi ritrovai in una selva oscura che la diritta via era smarrita' + expected = {'n': 5, 'm': 4, 'd': 3, 'c': 3, 'v': 4, 'r': 6, 'i': 9, 'u': 2, 's': 4, 'o': 4, 'l': 4, 'e': 6} + ''' + text = 'Nel Mezzo del caMmin Di nostra vita mi Ritrovai in una selva oscura che la diritta via era smarrita' + expected = {'n': 5, 'm': 4, 'd': 3, 'c': 3, 'v': 4, 'r': 6, 'i': 9, 'u': 2, 's': 4, 'o': 4, 'l': 4, 'e': 6} + return do_func2_tests(text, expected) + +def test_func2_3(): + ''' + text = 'To be or not to be that is the question Whether tis nobler in the mind to suffer The slings and arrows of outrageous fortune Or to take arms against a sea of troubles And by opposing end them' + expected = {'t': 18, 'b': 5, 'o': 16, 'n': 12, 'i': 8, 'q': 1, 'w': 2, 'm': 3, 's': 12, 'a': 10, 'f': 4, 'e': 16} + ''' + text = 'To be or not to be that is the question Whether tis nobler in the mind to suffer The slings and arrows of outrageous fortune Or to take arms against a sea of troubles And by opposing end them' + expected = {'t': 18, 'b': 5, 'o': 16, 'n': 12, 'i': 8, 'q': 1, 'w': 2, 'm': 3, 's': 12, 'a': 10, 'f': 4, 'e': 16} + return do_func2_tests(text, expected) + +def test_func2_4(): + text = 'FAR Out in THe unchArTEd baCKwaTers Of the uNFashIonaBlE End Of tHE westeRn SpirAl ARm of tHe GalaXY liEs A SMaLl UnrEgarded yelLow sUn OrbItiNg this at A DISTaNce OF rOUGHlY nINEtY TWO miLLioN Miles IS an utTerLy iNSiGnifIcAnt lITtle Blue greeN pLaneT whOse APE deSCenDeD lIfE forMS Are SO aMAZinglY prImitivE thaT tHeY sTill thiNk DigITal WAtcheS aRE A preTTy nEat IDEA This plaNet HAs or RAthEr HaD A probleM WHiCh wAs tHis mOSt Of thE peOple on It Were unHappy for PRETty mUcH OF the tIMe MAnY sOluTIonS Were suggEsTEd FOr THiS prOBLem buT most of These weRe lArGeLy COncERNEd WIth the MoVements oF smAlL GrEeN pieceS of pAPeR WhICh iS odD becaUse on tHe WhOLe It WASN t the SmaLL GReeN piEcEs oF paper tHAt Were uNHaPPY And SO the PrObLEm remaINeD LotS of ThE pEOpLe weRe meaN aNd mOst oF them weRe mIsErAble even tHe ONES WITh DIgital WaTches MAny weRE iNcReasiNgly of thE oPiNIoN that they D aLL madE A bIg MistAKe iN coMIng dOwn from the treEs in The firST PLace AND sOMe saiD THAt EVeN tHE trEEs HAd BEen A bad move and thAT no onE shoUld ever hAve LEft tHe ocEAns And THEn oNe ThUrSDAY NeArLY twO ThouSAND yearS afTeR ONE mAn HaD BEen NaIled to a tREE for sAying hOw greAt It woulD bE To be nIce to peOpLe FoR a CHangE oNe girL SiTtINg on hEr owN in A sMAlL CAfE in RICkmansWorth sUdDeNly rEaliZed WHAT it Was that haD BeEN GoING wroNG ALL This TiME aNd sHe FinaLly knEw hOw tHe WoRLd cOulD be MAdE A gOOD aNd hapPy pLAcE THiS TimE it waS Right IT WOUld WoRK and nO oNE Would hAvE to GEt naiLed to ANyThIng SaDly hOweVER BEfORe sHe Could gEt tO a pHone to TELL anyonE about iT A terriblY sTuPID CatAstrOPHe OcCUrrED aND the idEa wAs lOsT FOREvEr ThiS iS noT hEr sTOrY' + expected = {'f': 30, 'o': 97, 'i': 74, 't': 116, 'u': 27, 'b': 21, 'e': 144, 'w': 39, 's': 73, 'a': 113, 'g': 28, 'l': 57, 'y': 28, 'd': 49, 'r': 69, 'n': 87, 'm': 36, 'p': 26, 'h': 78, 'c': 27, 'k': 6} + return do_func2_tests(text, expected) + + +# ----------------------------------- FUNC. 3 ----------------------------------- # + +def do_func3_tests(ID, expected): + input_filename = f'func3/in_{ID}.txt' + output_filename = f'func3/your_output_{ID}.txt' + expected_filename = f'func3/expected_{ID}.txt' + + # remove the previous output each time if it is there + if os.path.exists(output_filename): + os.remove(output_filename) + + res = program.func3(input_filename, output_filename) + if res != expected: + my_print(f'''{'*'*50}\n[ERROR] Il valore ritornato è sbagliato! / The returned value is incorrect!''') + my_print(f'''Returned={res}, expected={expected}.\n{'*'*50}''') + return 0 + testlib.check_text_file(output_filename, expected_filename) + return 1 + + +def test_func3_1(): + ''' + textfile_in = 'func3/in_1.txt' + textfile_out = 'func3/your_output_1.txt' + expected = 'func3/expected_1.txt' + ''' + ID = 1 + expected = 5 + return do_func3_tests(ID, expected) + +def test_func3_2(): + ''' + textfile_in = 'func3/in_2.txt' + textfile_out = 'func3/your_output_2.txt' + expected = 'func3/expected_2.txt' + ''' + ID = 2 + expected = 11 + return do_func3_tests(ID, expected) + +def test_func3_3(): + ''' + textfile_in = 'func3/in_3.txt' + textfile_out = 'func3/your_output_3.txt' + expected = 'func3/expected_3.txt' + ''' + ID = 3 + expected = 37 + return do_func3_tests(ID, expected) + +def test_func3_4(): + ''' + textfile_in = 'func3/in_4.txt' + textfile_out = 'func3/your_output_4.txt' + expected = 'func3/expected_4.txt' + ''' + ID = 4 + expected = 51 + return do_func3_tests(ID, expected) + + +# ----------------------------------- FUNC. 4 ----------------------------------- # + + +def do_func4_tests(ID, expected): + input_filename = f'func4/in_{ID}.txt' + res = program.func4(input_filename) + if res != expected: + my_print(f'''{'*'*50}\n[ERROR] Il risultato è sbagliato! / The result is incorrect!\nReturned={res}, expected={expected}.\n{'*'*50}''') + return 0 + return 1 + + +def test_func4_1(): + ''' + input_filename = 'func4/in_1.txt' + expected = [[12, 8, 4], + [11, 7, 3], + [10, 6, 2], + [ 9, 5, 1]] + ''' + ID = 1 + expected = [[12, 8, 4], + [11, 7, 3], + [10, 6, 2], + [ 9, 5, 1]] + return do_func4_tests(ID, expected) + +def test_func4_2(): + ''' + input_filename = 'func4/in_2.txt' + expected = [[-694, 273, 473, 589, -278, -609], + [795, 699, 271, -660, -954, -893], + [626, -884, -391, 881, 247, 496]] + ''' + ID = 2 + expected = [[-694, 273, 473, 589, -278, -609], + [795, 699, 271, -660, -954, -893], + [626, -884, -391, 881, 247, 496]] + return do_func4_tests(ID, expected) + + +def test_func4_3(): + ''' + input_filename = 'func4/in_3.txt' + ''' + ID = 3 + expected = [[-104, -197, -250, 526, -450, -332, -332], + [398, -84, 962, 386, 531, 389, 226], + [30, -690, 912, 780, -420, -573, 116], + [407, 804, -472, -708, 163, -416, -555], + [-808, 949, 181, -869, 172, -206, 31], + [570, -168, -830, -715, 114, 401, 618], + [719, 452, -490, -416, 125, -230, -309], + [-911, 587, -161, -373, -90, -274, 892], + [678, 229, 274, 856, 957, -243, -939], + [537, 317, -894, 904, 760, -451, -897], + [862, -298, 76, -683, 476, 194, -791], + [-290, -661, -526, 494, -67, 920, -211], + [191, -872, 768, -382, 529, 102, -288], + [-526, 132, -457, 468, -79, -798, -289], + [-170, -694, -811, -671, 578, 235, 757], + [486, -945, 684, 862, 661, 175, 431], + [194, 706, -770, 442, 717, -909, -102], + [-247, -501, 824, -177, 709, -735, -311], + [-370, 38, 236, -988, 369, 866, -822], + [-40, -636, -403, 768, -428, -22, 246]] + return do_func4_tests(ID, expected) + +def test_func4_4(): + ''' + input_filename = 'func4/in_4.txt' + ''' + ID = 4 + expected = [[14, 685, -812, -522, 126, -840, 261, -384, 475, -639, -487, -782, 779], + [13, 615, 353, 637, -254, 817, -378, -791, 51, -116, -543, -675, 488], + [295, -987, -193, 287, 608, 648, -480, 578, 587, -810, -287, -183, 237], + [686, 259, 917, -457, -903, -598, 227, -505, 861, -879, 700, 596, -471], + [716, -434, 47, 587, 215, 404, 634, -803, -812, 693, 114, 783, 61], + [332, -60, -262, 657, 804, -591, 332, 71, 285, -939, 87, 696, -442], + [756, -921, -242, 500, 873, 32, -545, -734, -198, 235, -206, 175, 528], + [988, 842, -746, -553, 353, -682, 774, 301, -54, -328, 462, 166, -779], + [-525, 205, 152, 293, -585, 11, 825, -632, -321, 549, 748, -815, 448], + [603, 698, 520, 941, 730, 433, 13, 919, -907, -710, -491, 507, 431], + [233, 437, 621, 341, 681, 55, 916, 996, -405, -441, 274, 954, 468], + [-204, 22, 88, -119, -354, 107, -119, -441, -299, -129, 90, -251, 77], + [-936, -856, -795, 72, 654, 883, 239, -884, -751, 511, 83, -297, 911], + [850, -157, 855, 28, 564, -121, -882, -5, -968, 835, 251, -2, -830], + [55, -32, -63, 67, 978, 726, 818, 47, -1, 13, -744, -466, -89], + [676, -489, 370, -941, -127, 867, -150, -961, -26, -806, 233, 326, -581], + [562, 183, -88, -9, -418, -112, 841, -9, -893, -448, 262, -49, -702], + [-875, -587, -696, 451, -557, 664, 72, 409, 470, 812, 598, 360, -126], + [417, 369, -156, 476, -189, -865, 315, 25, -25, 595, -765, -211, 812], + [-381, 976, 569, 987, -651, 346, -967, 763, -860, 322, -529, -386, 819], + [252, -510, 453, 504, -258, 157, 777, 195, 94, -162, -563, -870, -810], + [286, -301, 312, -555, 311, -935, 882, -106, 867, 940, -24, 840, -394], + [-209, 304, -786, -282, -588, -953, 190, -116, 257, -320, -504, -805, 197]] + return do_func4_tests(ID, expected) + +# ----------------------------------- FUNC. 5 ----------------------------------- # + + +def do_test_func5(ID, W, H, expected): + txt_in = f'func5/in_{ID}.txt' + img_out = f'func5/your_image_{ID}.png' + img_exp = f'func5/expected_{ID}.png' + # remove the previous image each time if it is there + if os.path.exists(img_out): + os.remove(img_out) + # now run + res = program.func5(txt_in, W, H, img_out) + if res != expected: + my_print(f'''{'*'*50}\n[ERROR] Il numero di rettangoli e ellissi è sbagliato! / The number of rectangles and ellipses are incorrect.\nReturned={res}, expected={expected}.\n{'*'*50}''') + return 0 + testlib.check_img_file(img_out, img_exp) + return 2 + + +def test_func5_1(): + ''' + txt_file = func5/in_1.txt + output_imagefile = func5/your_image_1.png + width = 50 + heigth = 100 + expected_imagefile = func5/expected_1.png + expected = (2, 1) + ''' + ID = 1 + width = 50 + heigth = 100 + expected = (2, 1) + return do_test_func5(ID, width, heigth, expected) + +def test_func5_2(): + ''' + txt_file = func5/in_2.txt + output_imagefile = func5/your_image_2.png + width = 200 + heigth = 200 + expected_imagefile = func5/expected_2.png + expected = (4, 7) + ''' + ID = 2 + width = 200 + heigth = 200 + expected = (4, 7) + return do_test_func5(ID, width, heigth, expected) + + +def test_func5_3(): + ''' + txt_file = func5/in_3.txt + output_imagefile = func5/your_image_3.png + width = 300 + heigth = 400 + expected_imagefile = func5/expected_3.png + expected = (8, 13) + ''' + ID = 3 + width = 300 + heigth = 400 + expected = (8, 13) + return do_test_func5(ID, width, heigth, expected) + + +def test_func5_4(): + ''' + txt_file = func5/in_4.txt + output_imagefile = func5/your_image_4.png + width = 500 + heigth = 200 + expected_imagefile = func5/expected_4.png + expected = (32, 23) + ''' + ID = 4 + width = 500 + heigth = 200 + expected = (32, 23) + return do_test_func5(ID, width, heigth, expected) + +# ----------------------------------- EX. 1 ----------------------------------- # + +def do_test_ex1(rootlist, values, expected, sum_expected): + if not DEBUG: + root = nary_tree.NaryTree.fromList(rootlist.copy()) + try: + isrecursive.decorate_module(program) + program.ex1(root, values) + except isrecursive.RecursionDetectedError: + pass + else: + raise Exception("The program does not employ recursion / Il programma non adotta un approccio ricorsivo") + finally: + isrecursive.undecorate_module(program) + root = nary_tree.NaryTree.fromList(rootlist.copy()) + expected = nary_tree.NaryTree.fromList(expected.copy()) + res = program.ex1(root, values) + testlib.check(res, sum_expected, None, f'''{'*'*50}\n[ERROR]Le due somme tornate non sono corrette! / Incorrect sums!\nReturned={res}, expected={sum_expected}''') + testlib.check(root, expected, None, f'''{'*'*50}\n[ERROR]L'albero ritornato non è corretto! / Incorrect tree!\nReturned={root}, expected={expected}''') + return 2 + +def test_ex1_1(): + ''' + values: [-42, -80, 68, 2, 81, 75, 54, 48, -4, 5] + root: -7 | -42 + / | | | \ | + -10 -3 -8 -10 -5 | -80 + / \ | | | | + 6 -2 9 7 -9 | +68 + + expected: -49 | + / | | | \ | + -90 -83 -88 -90 -85 | + / \ | | | | + 74 66 77 75 59 | + total = -134 + ''' + root = [-7, [-10, [6], + [-2]], + [-3, [9]], + [-8, [7]], + [-10, [-9]], + [-5]] + values = [-42, -80, 68, 2, 81, 75, 54, 48, -4, 5] + expected = [-49, [-90, [74], + [66]], + [-83, [77]], + [-88, [75]], + [-90, [59]], + [-85]] + total = -134 + return do_test_ex1(root, values, expected, total) + +def test_ex1_2(): + root = [-53, [-5, [-87, [-21]], [-81], [67, [68, [-66, [-2]], [2, [-69], [52, [97]]], + [-12, [-16, [23], [-64, [60], [-43], [-71]], [89]], [97, [59, [93], [78]]]]], + [36, [75, [-81], [37, [31], [-47]], [-70, [27], [12]]], [-96, [22, [85], [67, [-12], + [17], [-8]], [95]], [-14]], [-24, [-87], [19], [99, [-25]]]], [-44, [0], [24, [-78]]]]]] + values = [-67, 89, -59, -68, 3, 44, 28, -45, 41, -99] + expected = [-120, [84, [-146, [-89]], [-140], [8, [0, [-63, [42]], [5, [-25], [96, [125]]], + [-9, [28, [51], [-36, [15], [-88], [-116]], [117]], [141, [87, [48], [33]]]]], + [-32, [78, [-37], [81, [59], [-19]], [-26, [55], [40]]], [-93, [66, [113], [95, + [-57], [-28], [-53]], [123]], [30]], [-21, [-43], [63], [143, [3]]]], [-112, [3], [27, [-34]]]]]] + total = 472 + return do_test_ex1(root, values, expected, total) + +def test_ex1_3(): + root = [-4, [-3, [-7, [-7, [1, [9], [2]], [-2], [2, [-10], [-5]], [3, [10]], [-6, [-3], + [0], [-8], [6], [0]]], [7, [-8, [-6], [3, [-8], [4], [3], [-8], [1]]], [-5, [10, [6], [-3], + [4], [2]], [8], [4], [-6]]]], [6, [-3, [9], [0], [7, [-4]], [-6]], [-3, [2, [9], [4], [5], + [-1], [1, [3], [-9, [-8], [10], [0], [2], [-5]], [9]]], [6, [-2]]], [3], + [3, [-5], [8], [-7], [5], [10]]], [-4], [-4, [-3, [-9, [10, [-5]]]], + [-9, [-6, [-8, [3], [-9]], [-3], [-9], [-6]], [-5, [8], [-2], [-1], [-4]], [10], [0]], + [-1, [8, [4, [-6], [2]], [2, [-2], [2], [-8], [7]], [-8], [-6, [5], [1, [-3], [-4], [0]], + [-8, [10], [1]]]]]]]] + values = [37, -80, -58, -6, -95, 48, 18, 16, 83, -41] + expected = [33, [-83, [-65, [-13, [-94, [57], [50]], [-97], [-93, [38], [43]], [-92, [58]], + [-101, [45], [48], [40], [54], [48]]], [1, [-103, [42], [51, [10], [22], [21], [10], [19]]], + [-100, [58, [24], [15], [22], [20]], [56], [52], [42]]]], [-52, [-9, [-86], [-95], [-88, [44]], + [-101]], [-9, [-93, [57], [52], [53], [47], [49, [21], [9, [8], [26], [16], [18], [11]], [27]]], + [-89, [46]]], [-3], [-3, [-100], [-87], [-102], [-90], [-85]]], [-62], [-62, [-9, [-104, [58, + [13]]]], [-15, [-101, [40, [21], [9]], [45], [39], [42]], [-100, [56], [46], [47], [44]], + [-85], [-95]], [-7, [-87, [52, [12], [20]], [50, [16], [20], [10], [25]], [40], + [42, [23], [19, [13], [12], [16]], [10, [26], [17]]]]]]]] + total = -314 + return do_test_ex1(root, values, expected, total) + + + +# ----------------------------------- EX.2 ----------------------------------- # +def do_ex2_test(directory, words, expected): + if not DEBUG: + try: + isrecursive.decorate_module(program) + program.ex2(directory, words) + except isrecursive.RecursionDetectedError: + pass + else: + raise Exception("The program does not employ recursion / Il programma non adotta un approccio ricorsivo") + finally: + isrecursive.undecorate_module(program) + + res = program.ex2(directory, words) + if res is None: return 0 + if res == expected: + return 2 + if set(res) == set(expected): + return 1 + my_print( + f'''{'*' * 50}\n[ERROR]La lista ritornata non è corretta! / The returned list is incorrect!!\nReturned={res}, expected={expected}''') + return 0 + +def test_ex2_1(): + directory = 'ex2' + words = ["cat", "dog"] + expected = [('dog', 10), ('cat', 5)] + return do_ex2_test(directory, words, expected) + + +def test_ex2_2(): + directory = 'ex2/A' + words = ["gnu", "cat", "fish"] + expected = [('cat', 3), ('fish', 3), ('gnu', 1)] + return do_ex2_test(directory, words, expected) + + +def test_ex2_3(): + directory = 'ex2' + words = ["bird", "dog", "gnu", "tuna"][::-1] + expected = [('dog', 10), ('bird', 8), ('tuna', 2), ('gnu', 1)] + return do_ex2_test(directory, words, expected) + +################################################################################ + +tests = [ + # TO RUN ONLY SOME OF THE TESTS, comment any of the following entries + # PER DISATTIVARE ALCUNI TEST, commentare gli elementi seguenti + test_func1_1, test_func1_2, test_func1_3, test_func1_4, + test_func2_1, test_func2_2, test_func2_3, test_func2_4, + test_func3_1, test_func3_2, test_func3_3, test_func3_4, + test_func4_1, test_func4_2, test_func4_3, test_func4_4, + test_func5_1, test_func5_2, test_func5_3, test_func5_4, + test_ex1_1, test_ex1_2, test_ex1_3, + test_ex2_1, test_ex2_2, test_ex2_3, + test_personal_data_entry, +] + + +if __name__ == '__main__': + if test_personal_data_entry() < 0: + print(f"{COL['RED']}PERSONAL INFO MISSING. PLEASE FILL THE INITIAL VARS WITH YOUR NAME SURNAME AND STUDENT_ID{COL['RST']}") + sys.exit() + testlib.runtests( tests, + verbose=True, + logfile='grade.csv', + stack_trace=DEBUG) + testlib.check_exam_constraints() + if 'matricola' in program.__dict__: + print(f"{COL['GREEN']}Nome: {program.nome}\nCognome: {program.cognome}\nMatricola: {program.matricola}{COL['RST']}") + elif 'student_id' in program.__dict__: + print(f"{COL['GREEN']}Name: {program.name}\nSurname: {program.surname}\nStudentID: {program.student_id}{COL['RST']}") + else: + print('we should not arrive here the matricola/student ID variable is not present in program.py') +################################################################################ diff --git a/Esami/2022-2023/Esame-7/images.py b/Esami/2022-2023/Esame-7/images.py new file mode 100644 index 0000000..427d01a --- /dev/null +++ b/Esami/2022-2023/Esame-7/images.py @@ -0,0 +1,31 @@ +''' +Funzioni di utilita' per leggere e salvare una immagine nella nostra codifica. +Utilities to load/save a PNG file to our encoding. +''' +import png + +def load(filename): + """ Carica la immagine PNG dal file 'filename'. Torna una lista di liste di pixel. + Ogni pixel è una tupla (R, G, B) dei 3 colori con valori tra 0 e 255. + Load a PNG image from file 'filename'. Return a list of lists of pixel. + Each pixel is a tuple (R, G, B) of its 3 colors with values in 0..255. + """ + with open(filename, mode='rb') as f: + reader = png.Reader(file=f) + w, h, png_img, _ = reader.asRGB8() + # ottimizzata leggermente + w *= 3 + return [ [ (line[i],line[i+1],line[i+2]) + for i in range(0, w, 3) ] + for line in png_img ] + + +def save(img, filename): + """ Salva l'immagine 'img' nel file PNG 'filename'. img è una lista di liste di pixel. + Ogni pixel è una tupla (R, G, B) dei 3 colori con valori tra 0 e 255. + Save the 'img' image in a 'filename' PNG file. img is a list of lists of pixel. + Each pixel is a tuple (R, G, B) of its 3 colors with values in 0..255. + """ + pngimg = png.from_array(img,'RGB') + pngimg.save(filename) + diff --git a/Esami/2022-2023/Esame-7/isrecursive.py b/Esami/2022-2023/Esame-7/isrecursive.py new file mode 100644 index 0000000..b26b88a --- /dev/null +++ b/Esami/2022-2023/Esame-7/isrecursive.py @@ -0,0 +1,89 @@ + +from __future__ import print_function +import types + +DEBUG=2 # many details +DEBUG=1 # the function call +DEBUG=0 # none + +class RecursionDetectedError(Exception): + pass + +def norecurse(func): + '''Decoratore che lancia una eccezione se la funzione è ricorsiva''' + func.called = False + def f(*args, **kwargs): + if DEBUG: print('calling', func.__name__, func.called, *args) + if func.called: + print("Recursion detected! in " + func.__name__) + func.called = False # if you are going to continue execution + raise RecursionDetectedError + func.called = True + result = func(*args, **kwargs) + func.called = False + if DEBUG: print('returning', result, 'from', func.__name__) + return result + return f + +def isRecursiveP(func): + '''Decoratore che setta l'attributo della funzione recursive=True se scopre la ricorsione nella sua esecuzione.''' + func.called = False + func.recursive = False + def f(*args, **kwargs): + if func.called: + #print "Recursion detected!" + func.recursive = True + func.called = True + result = func(*args, **kwargs) + func.called = False + return result + return f + +def decorate_function(f, dec): + '''applica il decoratore dec alla funzione f e ricorda la vecchia funzione''' + newf = dec(f) + newf.oldf = f + return newf + +def undecorate_function(f): + '''rimuove il decoratore dec dalla funzione f, se presente''' + return getattr(f, 'oldf', f) + +# TODO usare il modulo decorator + +def decorate_module(module, decorator=norecurse): + '''decora le funzioni ed i metodi delle classi definite nel modulo, per default con norecurse''' + for f in dir(module): + ff = getattr(module,f) + if getattr(ff, '__module__', None) == module.__name__: + if isinstance(ff, types.FunctionType): + if DEBUG>1: print('decorating', f) + ff = decorate_function(ff,decorator) + setattr(module,f,ff) + elif isinstance(ff, type): + if DEBUG>1: print('decorating', f, 'methods') + for m in dir(ff): + if DEBUG>1: print(' decorating',m) + mm = getattr(ff, m) + if isinstance(mm, types.FunctionType): + mm = decorate_function(mm, decorator) + setattr(ff, m, mm) + +def undecorate_module(module): + '''elimina le decorazioni messe prima''' + for f in dir(module): + ff = getattr(module,f) + if isinstance(ff, types.FunctionType): + if DEBUG>1: print('undecorating', f) + ff = undecorate_function(ff) + setattr(module,f,ff) + elif isinstance(ff, type): + if DEBUG>1: print('undecorating', f, 'methods') + for m in dir(ff): + if DEBUG>1: print(' undecorating',m) + mm = getattr(ff, m) + if isinstance(mm, types.FunctionType): + mm = undecorate_function(mm) + setattr(ff, m, mm) + + diff --git a/Esami/2022-2023/Esame-7/nary_tree.py b/Esami/2022-2023/Esame-7/nary_tree.py new file mode 100644 index 0000000..aed2921 --- /dev/null +++ b/Esami/2022-2023/Esame-7/nary_tree.py @@ -0,0 +1,74 @@ + +import copy +import random + +class NaryTree: + def __init__(self, V, sons=None): + self.value = V + self.sons = [] if sons is None else sons + +################### THE FOLLOWING METHODS ARE USED IN TESTS, AND ARE FORBIDDEN ##################### + + @classmethod + def fromList(cls, lista): + """ + Costruisce l'albero da una lista [value, figlio1, figlio2, ...] + In cui figlio1, figlio2, ... sono altrettanti alberi oppure il value None + :param lista: + :return: + """ + value, *sons = lista + sons = [ cls.fromList(son) for son in sons ] + return cls(value, sons) + + def toList(self): + """ + Converte l'albero in lista di liste [value, figlio1, figlio2, ...] + :return: + """ + sons = [son.toList() for son in self.sons] + return [self.value, *sons] + + def __eq__(self, other): + """ + Confronta due alberi + :param other: + :return: + """ + return other != None and \ + type(self) == type(other) and \ + self.value == other.value and \ + self.sons == other.sons + + def __repr__(self, livello=0): + """ + Stampa un albero con livello di indentazione dato + :param livello: indentazione + :return: + """ + indent = "| "*(livello+1) + res = f'{self.value}' + for son in self.sons: + res += f"\n{indent} {son.__repr__(livello+1)}" + return res + + @classmethod + def randomTree(cls, level): + """ + Genera un albero casuale di al più level livelli + :param level: + :return: + """ + V = random.randint(-100, 100) + if level >= 0: + sons = [cls.randomTree(level - random.randint(1,3)) + for _ in range(random.randint(0,3))] + else: + sons = [] + return cls(V, sons) + +if __name__ == '__main__': + pass + root = NaryTree.randomTree(10) + print(root) + print(root.toList()) diff --git a/Esami/2022-2023/Esame-7/png.py b/Esami/2022-2023/Esame-7/png.py new file mode 100644 index 0000000..33df0b8 --- /dev/null +++ b/Esami/2022-2023/Esame-7/png.py @@ -0,0 +1,2639 @@ +#!/usr/bin/env python + +from __future__ import print_function + +# png.py - PNG encoder/decoder in pure Python +# +# Copyright (C) 2006 Johann C. Rocholl +# Portions Copyright (C) 2009 David Jones +# And probably portions Copyright (C) 2006 Nicko van Someren +# +# Original concept by Johann C. Rocholl. +# +# LICENCE (MIT) +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation files +# (the "Software"), to deal in the Software without restriction, +# including without limitation the rights to use, copy, modify, merge, +# publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +""" +Pure Python PNG Reader/Writer + +This Python module implements support for PNG images (see PNG +specification at http://www.w3.org/TR/2003/REC-PNG-20031110/ ). It reads +and writes PNG files with all allowable bit depths +(1/2/4/8/16/24/32/48/64 bits per pixel) and colour combinations: +greyscale (1/2/4/8/16 bit); RGB, RGBA, LA (greyscale with alpha) with +8/16 bits per channel; colour mapped images (1/2/4/8 bit). +Adam7 interlacing is supported for reading and +writing. A number of optional chunks can be specified (when writing) +and understood (when reading): ``tRNS``, ``bKGD``, ``gAMA``. + +For help, type ``import png; help(png)`` in your python interpreter. + +A good place to start is the :class:`Reader` and :class:`Writer` +classes. + +Requires Python 2.3. Limited support is available for Python 2.2, but +not everything works. Best with Python 2.4 and higher. Installation is +trivial, but see the ``README.txt`` file (with the source distribution) +for details. + +This file can also be used as a command-line utility to convert +`Netpbm `_ PNM files to PNG, and the +reverse conversion from PNG to PNM. The interface is similar to that +of the ``pnmtopng`` program from Netpbm. Type ``python png.py --help`` +at the shell prompt for usage and a list of options. + +A note on spelling and terminology +---------------------------------- + +Generally British English spelling is used in the documentation. So +that's "greyscale" and "colour". This not only matches the author's +native language, it's also used by the PNG specification. + +The major colour models supported by PNG (and hence by PyPNG) are: +greyscale, RGB, greyscale--alpha, RGB--alpha. These are sometimes +referred to using the abbreviations: L, RGB, LA, RGBA. In this case +each letter abbreviates a single channel: *L* is for Luminance or Luma +or Lightness which is the channel used in greyscale images; *R*, *G*, +*B* stand for Red, Green, Blue, the components of a colour image; *A* +stands for Alpha, the opacity channel (used for transparency effects, +but higher values are more opaque, so it makes sense to call it +opacity). + +A note on formats +----------------- + +When getting pixel data out of this module (reading) and presenting +data to this module (writing) there are a number of ways the data could +be represented as a Python value. Generally this module uses one of +three formats called "flat row flat pixel", "boxed row flat pixel", and +"boxed row boxed pixel". Basically the concern is whether each pixel +and each row comes in its own little tuple (box), or not. + +Consider an image that is 3 pixels wide by 2 pixels high, and each pixel +has RGB components: + +Boxed row flat pixel:: + + list([R,G,B, R,G,B, R,G,B], + [R,G,B, R,G,B, R,G,B]) + +Each row appears as its own list, but the pixels are flattened so +that three values for one pixel simply follow the three values for +the previous pixel. This is the most common format used, because it +provides a good compromise between space and convenience. PyPNG regards +itself as at liberty to replace any sequence type with any sufficiently +compatible other sequence type; in practice each row is an array (from +the array module), and the outer list is sometimes an iterator rather +than an explicit list (so that streaming is possible). + +Flat row flat pixel:: + + [R,G,B, R,G,B, R,G,B, + R,G,B, R,G,B, R,G,B] + +The entire image is one single giant sequence of colour values. +Generally an array will be used (to save space), not a list. + +Boxed row boxed pixel:: + + list([ (R,G,B), (R,G,B), (R,G,B) ], + [ (R,G,B), (R,G,B), (R,G,B) ]) + +Each row appears in its own list, but each pixel also appears in its own +tuple. A serious memory burn in Python. + +In all cases the top row comes first, and for each row the pixels are +ordered from left-to-right. Within a pixel the values appear in the +order, R-G-B-A (or L-A for greyscale--alpha). + +There is a fourth format, mentioned because it is used internally, +is close to what lies inside a PNG file itself, and has some support +from the public API. This format is called packed. When packed, +each row is a sequence of bytes (integers from 0 to 255), just as +it is before PNG scanline filtering is applied. When the bit depth +is 8 this is essentially the same as boxed row flat pixel; when the +bit depth is less than 8, several pixels are packed into each byte; +when the bit depth is 16 (the only value more than 8 that is supported +by the PNG image format) each pixel value is decomposed into 2 bytes +(and `packed` is a misnomer). This format is used by the +:meth:`Writer.write_packed` method. It isn't usually a convenient +format, but may be just right if the source data for the PNG image +comes from something that uses a similar format (for example, 1-bit +BMPs, or another PNG file). + +And now, my famous members +-------------------------- +""" + +__version__ = "0.0.18" + +import itertools +import math +import re +# http://www.python.org/doc/2.4.4/lib/module-operator.html +import operator +import struct +import sys +# http://www.python.org/doc/2.4.4/lib/module-warnings.html +import warnings +import zlib + +from array import array +from functools import reduce + +try: + # `cpngfilters` is a Cython module: it must be compiled by + # Cython for this import to work. + # If this import does work, then it overrides pure-python + # filtering functions defined later in this file (see `class + # pngfilters`). + import cpngfilters as pngfilters +except ImportError: + pass + + +__all__ = ['Image', 'Reader', 'Writer', 'write_chunks', 'from_array'] + + +# The PNG signature. +# http://www.w3.org/TR/PNG/#5PNG-file-signature +_signature = struct.pack('8B', 137, 80, 78, 71, 13, 10, 26, 10) + +_adam7 = ((0, 0, 8, 8), + (4, 0, 8, 8), + (0, 4, 4, 8), + (2, 0, 4, 4), + (0, 2, 2, 4), + (1, 0, 2, 2), + (0, 1, 1, 2)) + +def group(s, n): + # See http://www.python.org/doc/2.6/library/functions.html#zip + return list(zip(*[iter(s)]*n)) + +def isarray(x): + return isinstance(x, array) + +def tostring(row): + return row.tobytes() + #return row.tostring() + +def interleave_planes(ipixels, apixels, ipsize, apsize): + """ + Interleave (colour) planes, e.g. RGB + A = RGBA. + + Return an array of pixels consisting of the `ipsize` elements of + data from each pixel in `ipixels` followed by the `apsize` elements + of data from each pixel in `apixels`. Conventionally `ipixels` + and `apixels` are byte arrays so the sizes are bytes, but it + actually works with any arrays of the same type. The returned + array is the same type as the input arrays which should be the + same type as each other. + """ + + itotal = len(ipixels) + atotal = len(apixels) + newtotal = itotal + atotal + newpsize = ipsize + apsize + # Set up the output buffer + # See http://www.python.org/doc/2.4.4/lib/module-array.html#l2h-1356 + out = array(ipixels.typecode) + # It's annoying that there is no cheap way to set the array size :-( + out.extend(ipixels) + out.extend(apixels) + # Interleave in the pixel data + for i in range(ipsize): + out[i:newtotal:newpsize] = ipixels[i:itotal:ipsize] + for i in range(apsize): + out[i+ipsize:newtotal:newpsize] = apixels[i:atotal:apsize] + return out + +def check_palette(palette): + """Check a palette argument (to the :class:`Writer` class) + for validity. Returns the palette as a list if okay; raises an + exception otherwise. + """ + + # None is the default and is allowed. + if palette is None: + return None + + p = list(palette) + if not (0 < len(p) <= 256): + raise ValueError("a palette must have between 1 and 256 entries") + seen_triple = False + for i,t in enumerate(p): + if len(t) not in (3,4): + raise ValueError( + "palette entry %d: entries must be 3- or 4-tuples." % i) + if len(t) == 3: + seen_triple = True + if seen_triple and len(t) == 4: + raise ValueError( + "palette entry %d: all 4-tuples must precede all 3-tuples" % i) + for x in t: + if int(x) != x or not(0 <= x <= 255): + raise ValueError( + "palette entry %d: values must be integer: 0 <= x <= 255" % i) + return p + +def check_sizes(size, width, height): + """Check that these arguments, in supplied, are consistent. + Return a (width, height) pair. + """ + + if not size: + return width, height + + if len(size) != 2: + raise ValueError( + "size argument should be a pair (width, height)") + if width is not None and width != size[0]: + raise ValueError( + "size[0] (%r) and width (%r) should match when both are used." + % (size[0], width)) + if height is not None and height != size[1]: + raise ValueError( + "size[1] (%r) and height (%r) should match when both are used." + % (size[1], height)) + return size + +def check_color(c, greyscale, which): + """Checks that a colour argument for transparent or + background options is the right form. Returns the colour + (which, if it's a bar integer, is "corrected" to a 1-tuple). + """ + + if c is None: + return c + if greyscale: + try: + len(c) + except TypeError: + c = (c,) + if len(c) != 1: + raise ValueError("%s for greyscale must be 1-tuple" % + which) + if not isinteger(c[0]): + raise ValueError( + "%s colour for greyscale must be integer" % which) + else: + if not (len(c) == 3 and + isinteger(c[0]) and + isinteger(c[1]) and + isinteger(c[2])): + raise ValueError( + "%s colour must be a triple of integers" % which) + return c + +class Error(Exception): + def __str__(self): + return self.__class__.__name__ + ': ' + ' '.join(self.args) + +class FormatError(Error): + """Problem with input file format. In other words, PNG file does + not conform to the specification in some way and is invalid. + """ + +class ChunkError(FormatError): + pass + + +class Writer: + """ + PNG encoder in pure Python. + """ + + def __init__(self, width=None, height=None, + size=None, + greyscale=False, + alpha=False, + bitdepth=8, + palette=None, + transparent=None, + background=None, + gamma=None, + compression=None, + interlace=False, + bytes_per_sample=None, # deprecated + planes=None, + colormap=None, + maxval=None, + chunk_limit=2**20, + x_pixels_per_unit = None, + y_pixels_per_unit = None, + unit_is_meter = False): + """ + Create a PNG encoder object. + + Arguments: + + width, height + Image size in pixels, as two separate arguments. + size + Image size (w,h) in pixels, as single argument. + greyscale + Input data is greyscale, not RGB. + alpha + Input data has alpha channel (RGBA or LA). + bitdepth + Bit depth: from 1 to 16. + palette + Create a palette for a colour mapped image (colour type 3). + transparent + Specify a transparent colour (create a ``tRNS`` chunk). + background + Specify a default background colour (create a ``bKGD`` chunk). + gamma + Specify a gamma value (create a ``gAMA`` chunk). + compression + zlib compression level: 0 (none) to 9 (more compressed); + default: -1 or None. + interlace + Create an interlaced image. + chunk_limit + Write multiple ``IDAT`` chunks to save memory. + x_pixels_per_unit + Number of pixels a unit along the x axis (write a + `pHYs` chunk). + y_pixels_per_unit + Number of pixels a unit along the y axis (write a + `pHYs` chunk). Along with `x_pixel_unit`, this gives + the pixel size ratio. + unit_is_meter + `True` to indicate that the unit (for the `pHYs` + chunk) is metre. + + The image size (in pixels) can be specified either by using the + `width` and `height` arguments, or with the single `size` + argument. If `size` is used it should be a pair (*width*, + *height*). + + `greyscale` and `alpha` are booleans that specify whether + an image is greyscale (or colour), and whether it has an + alpha channel (or not). + + `bitdepth` specifies the bit depth of the source pixel values. + Each source pixel value must be an integer between 0 and + ``2**bitdepth-1``. For example, 8-bit images have values + between 0 and 255. PNG only stores images with bit depths of + 1,2,4,8, or 16. When `bitdepth` is not one of these values, + the next highest valid bit depth is selected, and an ``sBIT`` + (significant bits) chunk is generated that specifies the + original precision of the source image. In this case the + supplied pixel values will be rescaled to fit the range of + the selected bit depth. + + The details of which bit depth / colour model combinations the + PNG file format supports directly, are somewhat arcane + (refer to the PNG specification for full details). Briefly: + "small" bit depths (1,2,4) are only allowed with greyscale and + colour mapped images; colour mapped images cannot have bit depth + 16. + + For colour mapped images (in other words, when the `palette` + argument is specified) the `bitdepth` argument must match one of + the valid PNG bit depths: 1, 2, 4, or 8. (It is valid to have a + PNG image with a palette and an ``sBIT`` chunk, but the meaning + is slightly different; it would be awkward to press the + `bitdepth` argument into service for this.) + + The `palette` option, when specified, causes a colour + mapped image to be created: the PNG colour type is set to 3; + `greyscale` must not be set; `alpha` must not be set; + `transparent` must not be set; the bit depth must be 1,2,4, + or 8. When a colour mapped image is created, the pixel values + are palette indexes and the `bitdepth` argument specifies the + size of these indexes (not the size of the colour values in + the palette). + + The palette argument value should be a sequence of 3- or + 4-tuples. 3-tuples specify RGB palette entries; 4-tuples + specify RGBA palette entries. If both 4-tuples and 3-tuples + appear in the sequence then all the 4-tuples must come + before all the 3-tuples. A ``PLTE`` chunk is created; if there + are 4-tuples then a ``tRNS`` chunk is created as well. The + ``PLTE`` chunk will contain all the RGB triples in the same + sequence; the ``tRNS`` chunk will contain the alpha channel for + all the 4-tuples, in the same sequence. Palette entries + are always 8-bit. + + If specified, the `transparent` and `background` parameters must + be a tuple with three integer values for red, green, blue, or + a simple integer (or singleton tuple) for a greyscale image. + + If specified, the `gamma` parameter must be a positive number + (generally, a `float`). A ``gAMA`` chunk will be created. + Note that this will not change the values of the pixels as + they appear in the PNG file, they are assumed to have already + been converted appropriately for the gamma specified. + + The `compression` argument specifies the compression level to + be used by the ``zlib`` module. Values from 1 to 9 specify + compression, with 9 being "more compressed" (usually smaller + and slower, but it doesn't always work out that way). 0 means + no compression. -1 and ``None`` both mean that the default + level of compession will be picked by the ``zlib`` module + (which is generally acceptable). + + If `interlace` is true then an interlaced image is created + (using PNG's so far only interace method, *Adam7*). This does + not affect how the pixels should be presented to the encoder, + rather it changes how they are arranged into the PNG file. + On slow connexions interlaced images can be partially decoded + by the browser to give a rough view of the image that is + successively refined as more image data appears. + + .. note :: + + Enabling the `interlace` option requires the entire image + to be processed in working memory. + + `chunk_limit` is used to limit the amount of memory used whilst + compressing the image. In order to avoid using large amounts of + memory, multiple ``IDAT`` chunks may be created. + """ + + # At the moment the `planes` argument is ignored; + # its purpose is to act as a dummy so that + # ``Writer(x, y, **info)`` works, where `info` is a dictionary + # returned by Reader.read and friends. + # Ditto for `colormap`. + + width, height = check_sizes(size, width, height) + del size + + if width <= 0 or height <= 0: + raise ValueError("width and height must be greater than zero") + if not isinteger(width) or not isinteger(height): + raise ValueError("width and height must be integers") + # http://www.w3.org/TR/PNG/#7Integers-and-byte-order + if width > 2**32-1 or height > 2**32-1: + raise ValueError("width and height cannot exceed 2**32-1") + + if alpha and transparent is not None: + raise ValueError( + "transparent colour not allowed with alpha channel") + + if bytes_per_sample is not None: + warnings.warn('please use bitdepth instead of bytes_per_sample', + DeprecationWarning) + if bytes_per_sample not in (0.125, 0.25, 0.5, 1, 2): + raise ValueError( + "bytes per sample must be .125, .25, .5, 1, or 2") + bitdepth = int(8*bytes_per_sample) + del bytes_per_sample + if not isinteger(bitdepth) or bitdepth < 1 or 16 < bitdepth: + raise ValueError("bitdepth (%r) must be a positive integer <= 16" % + bitdepth) + + self.rescale = None + palette = check_palette(palette) + if palette: + if bitdepth not in (1,2,4,8): + raise ValueError("with palette, bitdepth must be 1, 2, 4, or 8") + if transparent is not None: + raise ValueError("transparent and palette not compatible") + if alpha: + raise ValueError("alpha and palette not compatible") + if greyscale: + raise ValueError("greyscale and palette not compatible") + else: + # No palette, check for sBIT chunk generation. + if alpha or not greyscale: + if bitdepth not in (8,16): + targetbitdepth = (8,16)[bitdepth > 8] + self.rescale = (bitdepth, targetbitdepth) + bitdepth = targetbitdepth + del targetbitdepth + else: + assert greyscale + assert not alpha + if bitdepth not in (1,2,4,8,16): + if bitdepth > 8: + targetbitdepth = 16 + elif bitdepth == 3: + targetbitdepth = 4 + else: + assert bitdepth in (5,6,7) + targetbitdepth = 8 + self.rescale = (bitdepth, targetbitdepth) + bitdepth = targetbitdepth + del targetbitdepth + + if bitdepth < 8 and (alpha or not greyscale and not palette): + raise ValueError( + "bitdepth < 8 only permitted with greyscale or palette") + if bitdepth > 8 and palette: + raise ValueError( + "bit depth must be 8 or less for images with palette") + + transparent = check_color(transparent, greyscale, 'transparent') + background = check_color(background, greyscale, 'background') + + # It's important that the true boolean values (greyscale, alpha, + # colormap, interlace) are converted to bool because Iverson's + # convention is relied upon later on. + self.width = width + self.height = height + self.transparent = transparent + self.background = background + self.gamma = gamma + self.greyscale = bool(greyscale) + self.alpha = bool(alpha) + self.colormap = bool(palette) + self.bitdepth = int(bitdepth) + self.compression = compression + self.chunk_limit = chunk_limit + self.interlace = bool(interlace) + self.palette = palette + self.x_pixels_per_unit = x_pixels_per_unit + self.y_pixels_per_unit = y_pixels_per_unit + self.unit_is_meter = bool(unit_is_meter) + + self.color_type = 4*self.alpha + 2*(not greyscale) + 1*self.colormap + assert self.color_type in (0,2,3,4,6) + + self.color_planes = (3,1)[self.greyscale or self.colormap] + self.planes = self.color_planes + self.alpha + # :todo: fix for bitdepth < 8 + self.psize = (self.bitdepth/8) * self.planes + + def make_palette(self): + """Create the byte sequences for a ``PLTE`` and if necessary a + ``tRNS`` chunk. Returned as a pair (*p*, *t*). *t* will be + ``None`` if no ``tRNS`` chunk is necessary. + """ + + p = array('B') + t = array('B') + + for x in self.palette: + p.extend(x[0:3]) + if len(x) > 3: + t.append(x[3]) + p = tostring(p) + t = tostring(t) + if t: + return p,t + return p,None + + def write(self, outfile, rows): + """Write a PNG image to the output file. `rows` should be + an iterable that yields each row in boxed row flat pixel + format. The rows should be the rows of the original image, + so there should be ``self.height`` rows of ``self.width * + self.planes`` values. If `interlace` is specified (when + creating the instance), then an interlaced PNG file will + be written. Supply the rows in the normal image order; + the interlacing is carried out internally. + + .. note :: + + Interlacing will require the entire image to be in working + memory. + """ + + if self.interlace: + fmt = 'BH'[self.bitdepth > 8] + a = array(fmt, itertools.chain(*rows)) + return self.write_array(outfile, a) + + nrows = self.write_passes(outfile, rows) + if nrows != self.height: + raise ValueError( + "rows supplied (%d) does not match height (%d)" % + (nrows, self.height)) + + def write_passes(self, outfile, rows, packed=False): + """ + Write a PNG image to the output file. + + Most users are expected to find the :meth:`write` or + :meth:`write_array` method more convenient. + + The rows should be given to this method in the order that + they appear in the output file. For straightlaced images, + this is the usual top to bottom ordering, but for interlaced + images the rows should have already been interlaced before + passing them to this function. + + `rows` should be an iterable that yields each row. When + `packed` is ``False`` the rows should be in boxed row flat pixel + format; when `packed` is ``True`` each row should be a packed + sequence of bytes. + """ + + # http://www.w3.org/TR/PNG/#5PNG-file-signature + outfile.write(_signature) + + # http://www.w3.org/TR/PNG/#11IHDR + write_chunk(outfile, b'IHDR', + struct.pack("!2I5B", self.width, self.height, + self.bitdepth, self.color_type, + 0, 0, self.interlace)) + + # See :chunk:order + # http://www.w3.org/TR/PNG/#11gAMA + if self.gamma is not None: + write_chunk(outfile, b'gAMA', + struct.pack("!L", int(round(self.gamma*1e5)))) + + # See :chunk:order + # http://www.w3.org/TR/PNG/#11sBIT + if self.rescale: + write_chunk(outfile, b'sBIT', + struct.pack('%dB' % self.planes, + *[self.rescale[0]]*self.planes)) + + # :chunk:order: Without a palette (PLTE chunk), ordering is + # relatively relaxed. With one, gAMA chunk must precede PLTE + # chunk which must precede tRNS and bKGD. + # See http://www.w3.org/TR/PNG/#5ChunkOrdering + if self.palette: + p,t = self.make_palette() + write_chunk(outfile, b'PLTE', p) + if t: + # tRNS chunk is optional. Only needed if palette entries + # have alpha. + write_chunk(outfile, b'tRNS', t) + + # http://www.w3.org/TR/PNG/#11tRNS + if self.transparent is not None: + if self.greyscale: + write_chunk(outfile, b'tRNS', + struct.pack("!1H", *self.transparent)) + else: + write_chunk(outfile, b'tRNS', + struct.pack("!3H", *self.transparent)) + + # http://www.w3.org/TR/PNG/#11bKGD + if self.background is not None: + if self.greyscale: + write_chunk(outfile, b'bKGD', + struct.pack("!1H", *self.background)) + else: + write_chunk(outfile, b'bKGD', + struct.pack("!3H", *self.background)) + + # http://www.w3.org/TR/PNG/#11pHYs + if self.x_pixels_per_unit is not None and self.y_pixels_per_unit is not None: + tup = (self.x_pixels_per_unit, self.y_pixels_per_unit, int(self.unit_is_meter)) + write_chunk(outfile, b'pHYs', struct.pack("!LLB",*tup)) + + # http://www.w3.org/TR/PNG/#11IDAT + if self.compression is not None: + compressor = zlib.compressobj(self.compression) + else: + compressor = zlib.compressobj() + + # Choose an extend function based on the bitdepth. The extend + # function packs/decomposes the pixel values into bytes and + # stuffs them onto the data array. + data = array('B') + if self.bitdepth == 8 or packed: + extend = data.extend + elif self.bitdepth == 16: + # Decompose into bytes + def extend(sl): + fmt = '!%dH' % len(sl) + data.extend(array('B', struct.pack(fmt, *sl))) + else: + # Pack into bytes + assert self.bitdepth < 8 + # samples per byte + spb = int(8/self.bitdepth) + def extend(sl): + a = array('B', sl) + # Adding padding bytes so we can group into a whole + # number of spb-tuples. + l = float(len(a)) + extra = math.ceil(l / float(spb))*spb - l + a.extend([0]*int(extra)) + # Pack into bytes + l = group(a, spb) + l = [reduce(lambda x,y: + (x << self.bitdepth) + y, e) for e in l] + data.extend(l) + if self.rescale: + oldextend = extend + factor = \ + float(2**self.rescale[1]-1) / float(2**self.rescale[0]-1) + def extend(sl): + oldextend([int(round(factor*x)) for x in sl]) + + # Build the first row, testing mostly to see if we need to + # changed the extend function to cope with NumPy integer types + # (they cause our ordinary definition of extend to fail, so we + # wrap it). See + # http://code.google.com/p/pypng/issues/detail?id=44 + enumrows = enumerate(rows) + del rows + + # First row's filter type. + data.append(0) + # :todo: Certain exceptions in the call to ``.next()`` or the + # following try would indicate no row data supplied. + # Should catch. + i,row = next(enumrows) + try: + # If this fails... + extend(row) + except: + # ... try a version that converts the values to int first. + # Not only does this work for the (slightly broken) NumPy + # types, there are probably lots of other, unknown, "nearly" + # int types it works for. + def wrapmapint(f): + return lambda sl: f([int(x) for x in sl]) + extend = wrapmapint(extend) + del wrapmapint + extend(row) + + for i,row in enumrows: + # Add "None" filter type. Currently, it's essential that + # this filter type be used for every scanline as we do not + # mark the first row of a reduced pass image; that means we + # could accidentally compute the wrong filtered scanline if + # we used "up", "average", or "paeth" on such a line. + data.append(0) + extend(row) + if len(data) > self.chunk_limit: + compressed = compressor.compress(tostring(data)) + if len(compressed): + write_chunk(outfile, b'IDAT', compressed) + # Because of our very witty definition of ``extend``, + # above, we must re-use the same ``data`` object. Hence + # we use ``del`` to empty this one, rather than create a + # fresh one (which would be my natural FP instinct). + del data[:] + if len(data): + compressed = compressor.compress(tostring(data)) + else: + compressed = b'' + flushed = compressor.flush() + if len(compressed) or len(flushed): + write_chunk(outfile, b'IDAT', compressed + flushed) + # http://www.w3.org/TR/PNG/#11IEND + write_chunk(outfile, b'IEND') + return i+1 + + def write_array(self, outfile, pixels): + """ + Write an array in flat row flat pixel format as a PNG file on + the output file. See also :meth:`write` method. + """ + + if self.interlace: + self.write_passes(outfile, self.array_scanlines_interlace(pixels)) + else: + self.write_passes(outfile, self.array_scanlines(pixels)) + + def write_packed(self, outfile, rows): + """ + Write PNG file to `outfile`. The pixel data comes from `rows` + which should be in boxed row packed format. Each row should be + a sequence of packed bytes. + + Technically, this method does work for interlaced images but it + is best avoided. For interlaced images, the rows should be + presented in the order that they appear in the file. + + This method should not be used when the source image bit depth + is not one naturally supported by PNG; the bit depth should be + 1, 2, 4, 8, or 16. + """ + + if self.rescale: + raise Error("write_packed method not suitable for bit depth %d" % + self.rescale[0]) + return self.write_passes(outfile, rows, packed=True) + + def convert_pnm(self, infile, outfile): + """ + Convert a PNM file containing raw pixel data into a PNG file + with the parameters set in the writer object. Works for + (binary) PGM, PPM, and PAM formats. + """ + + if self.interlace: + pixels = array('B') + pixels.fromfile(infile, + (self.bitdepth/8) * self.color_planes * + self.width * self.height) + self.write_passes(outfile, self.array_scanlines_interlace(pixels)) + else: + self.write_passes(outfile, self.file_scanlines(infile)) + + def convert_ppm_and_pgm(self, ppmfile, pgmfile, outfile): + """ + Convert a PPM and PGM file containing raw pixel data into a + PNG outfile with the parameters set in the writer object. + """ + pixels = array('B') + pixels.fromfile(ppmfile, + (self.bitdepth/8) * self.color_planes * + self.width * self.height) + apixels = array('B') + apixels.fromfile(pgmfile, + (self.bitdepth/8) * + self.width * self.height) + pixels = interleave_planes(pixels, apixels, + (self.bitdepth/8) * self.color_planes, + (self.bitdepth/8)) + if self.interlace: + self.write_passes(outfile, self.array_scanlines_interlace(pixels)) + else: + self.write_passes(outfile, self.array_scanlines(pixels)) + + def file_scanlines(self, infile): + """ + Generates boxed rows in flat pixel format, from the input file + `infile`. It assumes that the input file is in a "Netpbm-like" + binary format, and is positioned at the beginning of the first + pixel. The number of pixels to read is taken from the image + dimensions (`width`, `height`, `planes`) and the number of bytes + per value is implied by the image `bitdepth`. + """ + + # Values per row + vpr = self.width * self.planes + row_bytes = vpr + if self.bitdepth > 8: + assert self.bitdepth == 16 + row_bytes *= 2 + fmt = '>%dH' % vpr + def line(): + return array('H', struct.unpack(fmt, infile.read(row_bytes))) + else: + def line(): + scanline = array('B', infile.read(row_bytes)) + return scanline + for y in range(self.height): + yield line() + + def array_scanlines(self, pixels): + """ + Generates boxed rows (flat pixels) from flat rows (flat pixels) + in an array. + """ + + # Values per row + vpr = self.width * self.planes + stop = 0 + for y in range(self.height): + start = stop + stop = start + vpr + yield pixels[start:stop] + + def array_scanlines_interlace(self, pixels): + """ + Generator for interlaced scanlines from an array. `pixels` is + the full source image in flat row flat pixel format. The + generator yields each scanline of the reduced passes in turn, in + boxed row flat pixel format. + """ + + # http://www.w3.org/TR/PNG/#8InterlaceMethods + # Array type. + fmt = 'BH'[self.bitdepth > 8] + # Value per row + vpr = self.width * self.planes + for xstart, ystart, xstep, ystep in _adam7: + if xstart >= self.width: + continue + # Pixels per row (of reduced image) + ppr = int(math.ceil((self.width-xstart)/float(xstep))) + # number of values in reduced image row. + row_len = ppr*self.planes + for y in range(ystart, self.height, ystep): + if xstep == 1: + offset = y * vpr + yield pixels[offset:offset+vpr] + else: + row = array(fmt) + # There's no easier way to set the length of an array + row.extend(pixels[0:row_len]) + offset = y * vpr + xstart * self.planes + end_offset = (y+1) * vpr + skip = self.planes * xstep + for i in range(self.planes): + row[i::self.planes] = \ + pixels[offset+i:end_offset:skip] + yield row + +def write_chunk(outfile, tag, data=b''): + """ + Write a PNG chunk to the output file, including length and + checksum. + """ + + # http://www.w3.org/TR/PNG/#5Chunk-layout + outfile.write(struct.pack("!I", len(data))) + outfile.write(tag) + outfile.write(data) + checksum = zlib.crc32(tag) + checksum = zlib.crc32(data, checksum) + checksum &= 2**32-1 + outfile.write(struct.pack("!I", checksum)) + +def write_chunks(out, chunks): + """Create a PNG file by writing out the chunks.""" + + out.write(_signature) + for chunk in chunks: + write_chunk(out, *chunk) + +def filter_scanline(type, line, fo, prev=None): + """Apply a scanline filter to a scanline. `type` specifies the + filter type (0 to 4); `line` specifies the current (unfiltered) + scanline as a sequence of bytes; `prev` specifies the previous + (unfiltered) scanline as a sequence of bytes. `fo` specifies the + filter offset; normally this is size of a pixel in bytes (the number + of bytes per sample times the number of channels), but when this is + < 1 (for bit depths < 8) then the filter offset is 1. + """ + + assert 0 <= type < 5 + + # The output array. Which, pathetically, we extend one-byte at a + # time (fortunately this is linear). + out = array('B', [type]) + + def sub(): + ai = -fo + for x in line: + if ai >= 0: + x = (x - line[ai]) & 0xff + out.append(x) + ai += 1 + def up(): + for i,x in enumerate(line): + x = (x - prev[i]) & 0xff + out.append(x) + def average(): + ai = -fo + for i,x in enumerate(line): + if ai >= 0: + x = (x - ((line[ai] + prev[i]) >> 1)) & 0xff + else: + x = (x - (prev[i] >> 1)) & 0xff + out.append(x) + ai += 1 + def paeth(): + # http://www.w3.org/TR/PNG/#9Filter-type-4-Paeth + ai = -fo # also used for ci + for i,x in enumerate(line): + a = 0 + b = prev[i] + c = 0 + + if ai >= 0: + a = line[ai] + c = prev[ai] + p = a + b - c + pa = abs(p - a) + pb = abs(p - b) + pc = abs(p - c) + if pa <= pb and pa <= pc: + Pr = a + elif pb <= pc: + Pr = b + else: + Pr = c + + x = (x - Pr) & 0xff + out.append(x) + ai += 1 + + if not prev: + # We're on the first line. Some of the filters can be reduced + # to simpler cases which makes handling the line "off the top" + # of the image simpler. "up" becomes "none"; "paeth" becomes + # "left" (non-trivial, but true). "average" needs to be handled + # specially. + if type == 2: # "up" + type = 0 + elif type == 3: + prev = [0]*len(line) + elif type == 4: # "paeth" + type = 1 + if type == 0: + out.extend(line) + elif type == 1: + sub() + elif type == 2: + up() + elif type == 3: + average() + else: # type == 4 + paeth() + return out + + +# Regex for decoding mode string +RegexModeDecode = re.compile("(LA?|RGBA?);?([0-9]*)", flags=re.IGNORECASE) + +def from_array(a, mode=None, info={}): + """Create a PNG :class:`Image` object from a 2- or 3-dimensional + array. One application of this function is easy PIL-style saving: + ``png.from_array(pixels, 'L').save('foo.png')``. + + Unless they are specified using the *info* parameter, the PNG's + height and width are taken from the array size. For a 3 dimensional + array the first axis is the height; the second axis is the width; + and the third axis is the channel number. Thus an RGB image that is + 16 pixels high and 8 wide will use an array that is 16x8x3. For 2 + dimensional arrays the first axis is the height, but the second axis + is ``width*channels``, so an RGB image that is 16 pixels high and 8 + wide will use a 2-dimensional array that is 16x24 (each row will be + 8*3 = 24 sample values). + + *mode* is a string that specifies the image colour format in a + PIL-style mode. It can be: + + ``'L'`` + greyscale (1 channel) + ``'LA'`` + greyscale with alpha (2 channel) + ``'RGB'`` + colour image (3 channel) + ``'RGBA'`` + colour image with alpha (4 channel) + + The mode string can also specify the bit depth (overriding how this + function normally derives the bit depth, see below). Appending + ``';16'`` to the mode will cause the PNG to be 16 bits per channel; + any decimal from 1 to 16 can be used to specify the bit depth. + + When a 2-dimensional array is used *mode* determines how many + channels the image has, and so allows the width to be derived from + the second array dimension. + + The array is expected to be a ``numpy`` array, but it can be any + suitable Python sequence. For example, a list of lists can be used: + ``png.from_array([[0, 255, 0], [255, 0, 255]], 'L')``. The exact + rules are: ``len(a)`` gives the first dimension, height; + ``len(a[0])`` gives the second dimension; ``len(a[0][0])`` gives the + third dimension, unless an exception is raised in which case a + 2-dimensional array is assumed. It's slightly more complicated than + that because an iterator of rows can be used, and it all still + works. Using an iterator allows data to be streamed efficiently. + + The bit depth of the PNG is normally taken from the array element's + datatype (but if *mode* specifies a bitdepth then that is used + instead). The array element's datatype is determined in a way which + is supposed to work both for ``numpy`` arrays and for Python + ``array.array`` objects. A 1 byte datatype will give a bit depth of + 8, a 2 byte datatype will give a bit depth of 16. If the datatype + does not have an implicit size, for example it is a plain Python + list of lists, as above, then a default of 8 is used. + + The *info* parameter is a dictionary that can be used to specify + metadata (in the same style as the arguments to the + :class:`png.Writer` class). For this function the keys that are + useful are: + + height + overrides the height derived from the array dimensions and allows + *a* to be an iterable. + width + overrides the width derived from the array dimensions. + bitdepth + overrides the bit depth derived from the element datatype (but + must match *mode* if that also specifies a bit depth). + + Generally anything specified in the + *info* dictionary will override any implicit choices that this + function would otherwise make, but must match any explicit ones. + For example, if the *info* dictionary has a ``greyscale`` key then + this must be true when mode is ``'L'`` or ``'LA'`` and false when + mode is ``'RGB'`` or ``'RGBA'``. + """ + + # We abuse the *info* parameter by modifying it. Take a copy here. + # (Also typechecks *info* to some extent). + info = dict(info) + + # Syntax check mode string. + match = RegexModeDecode.match(mode) + if not match: + raise Error("mode string should be 'RGB' or 'L;16' or similar.") + + mode, bitdepth = match.groups() + alpha = 'A' in mode + if bitdepth: + bitdepth = int(bitdepth) + + # Colour format. + if 'greyscale' in info: + if bool(info['greyscale']) != ('L' in mode): + raise Error("info['greyscale'] should match mode.") + info['greyscale'] = 'L' in mode + + if 'alpha' in info: + if bool(info['alpha']) != alpha: + raise Error("info['alpha'] should match mode.") + info['alpha'] = alpha + + # Get bitdepth from *mode* if possible. + if bitdepth: + if info.get("bitdepth") and bitdepth != info['bitdepth']: + raise Error("bitdepth (%d) should match bitdepth of info (%d)." % + (bitdepth, info['bitdepth'])) + info['bitdepth'] = bitdepth + + # Fill in and/or check entries in *info*. + # Dimensions. + if 'size' in info: + assert len(info["size"]) == 2 + + # Check width, height, size all match where used. + for dimension,axis in [('width', 0), ('height', 1)]: + if dimension in info: + if info[dimension] != info['size'][axis]: + raise Error( + "info[%r] should match info['size'][%r]." % + (dimension, axis)) + info['width'],info['height'] = info['size'] + + if 'height' not in info: + try: + info['height'] = len(a) + except TypeError: + raise Error("len(a) does not work, supply info['height'] instead.") + + planes = len(mode) + if 'planes' in info: + if info['planes'] != planes: + raise Error("info['planes'] should match mode.") + + # In order to work out whether we the array is 2D or 3D we need its + # first row, which requires that we take a copy of its iterator. + # We may also need the first row to derive width and bitdepth. + a,t = itertools.tee(a) + row = next(t) + del t + try: + row[0][0] + threed = True + testelement = row[0] + except (IndexError, TypeError): + threed = False + testelement = row + if 'width' not in info: + if threed: + width = len(row) + else: + width = len(row) // planes + info['width'] = width + + if threed: + # Flatten the threed rows + a = (itertools.chain.from_iterable(x) for x in a) + + if 'bitdepth' not in info: + try: + dtype = testelement.dtype + # goto the "else:" clause. Sorry. + except AttributeError: + try: + # Try a Python array.array. + bitdepth = 8 * testelement.itemsize + except AttributeError: + # We can't determine it from the array element's + # datatype, use a default of 8. + bitdepth = 8 + else: + # If we got here without exception, we now assume that + # the array is a numpy array. + if dtype.kind == 'b': + bitdepth = 1 + else: + bitdepth = 8 * dtype.itemsize + info['bitdepth'] = bitdepth + + for thing in ["width", "height", "bitdepth", "greyscale", "alpha"]: + assert thing in info + + return Image(a, info) + +# So that refugee's from PIL feel more at home. Not documented. +fromarray = from_array + +class Image: + """A PNG image. You can create an :class:`Image` object from + an array of pixels by calling :meth:`png.from_array`. It can be + saved to disk with the :meth:`save` method. + """ + + def __init__(self, rows, info): + """ + .. note :: + + The constructor is not public. Please do not call it. + """ + + self.rows = rows + self.info = info + + def save(self, file): + """Save the image to *file*. If *file* looks like an open file + descriptor then it is used, otherwise it is treated as a + filename and a fresh file is opened. + + In general, you can only call this method once; after it has + been called the first time and the PNG image has been saved, the + source data will have been streamed, and cannot be streamed + again. + """ + + w = Writer(**self.info) + + try: + file.write + def close(): pass + except AttributeError: + file = open(file, 'wb') + def close(): file.close() + + try: + w.write(file, self.rows) + finally: + close() + +class _readable: + """ + A simple file-like interface for strings and arrays. + """ + + def __init__(self, buf): + self.buf = buf + self.offset = 0 + + def read(self, n): + r = self.buf[self.offset:self.offset+n] + if isarray(r): + r = r.tostring() + self.offset += n + return r + +try: + str(b'dummy', 'ascii') +except TypeError: + as_str = str +else: + def as_str(x): + return str(x, 'ascii') + +class Reader: + """ + PNG decoder in pure Python. + """ + + def __init__(self, _guess=None, **kw): + """ + Create a PNG decoder object. + + The constructor expects exactly one keyword argument. If you + supply a positional argument instead, it will guess the input + type. You can choose among the following keyword arguments: + + filename + Name of input file (a PNG file). + file + A file-like object (object with a read() method). + bytes + ``array`` or ``string`` with PNG data. + + """ + if ((_guess is not None and len(kw) != 0) or + (_guess is None and len(kw) != 1)): + raise TypeError("Reader() takes exactly 1 argument") + + # Will be the first 8 bytes, later on. See validate_signature. + self.signature = None + self.transparent = None + # A pair of (len,type) if a chunk has been read but its data and + # checksum have not (in other words the file position is just + # past the 4 bytes that specify the chunk type). See preamble + # method for how this is used. + self.atchunk = None + + if _guess is not None: + if isarray(_guess): + kw["bytes"] = _guess + elif isinstance(_guess, str): + kw["filename"] = _guess + elif hasattr(_guess, 'read'): + kw["file"] = _guess + + if "filename" in kw: + self.file = open(kw["filename"], "rb") + elif "file" in kw: + self.file = kw["file"] + elif "bytes" in kw: + self.file = _readable(kw["bytes"]) + else: + raise TypeError("expecting filename, file or bytes array") + + + def chunk(self, seek=None, lenient=False): + """ + Read the next PNG chunk from the input file; returns a + (*type*, *data*) tuple. *type* is the chunk's type as a + byte string (all PNG chunk types are 4 bytes long). + *data* is the chunk's data content, as a byte string. + + If the optional `seek` argument is + specified then it will keep reading chunks until it either runs + out of file or finds the type specified by the argument. Note + that in general the order of chunks in PNGs is unspecified, so + using `seek` can cause you to miss chunks. + + If the optional `lenient` argument evaluates to `True`, + checksum failures will raise warnings rather than exceptions. + """ + + self.validate_signature() + + while True: + # http://www.w3.org/TR/PNG/#5Chunk-layout + if not self.atchunk: + self.atchunk = self.chunklentype() + length, type = self.atchunk + self.atchunk = None + data = self.file.read(length) + if len(data) != length: + raise ChunkError('Chunk %s too short for required %i octets.' + % (type, length)) + checksum = self.file.read(4) + if len(checksum) != 4: + raise ChunkError('Chunk %s too short for checksum.' % type) + if seek and type != seek: + continue + verify = zlib.crc32(type) + verify = zlib.crc32(data, verify) + # Whether the output from zlib.crc32 is signed or not varies + # according to hideous implementation details, see + # http://bugs.python.org/issue1202 . + # We coerce it to be positive here (in a way which works on + # Python 2.3 and older). + verify &= 2**32 - 1 + verify = struct.pack('!I', verify) + if checksum != verify: + (a, ) = struct.unpack('!I', checksum) + (b, ) = struct.unpack('!I', verify) + message = "Checksum error in %s chunk: 0x%08X != 0x%08X." % (type, a, b) + if lenient: + warnings.warn(message, RuntimeWarning) + else: + raise ChunkError(message) + return type, data + + def chunks(self): + """Return an iterator that will yield each chunk as a + (*chunktype*, *content*) pair. + """ + + while True: + t,v = self.chunk() + yield t,v + if t == b'IEND': + break + + def undo_filter(self, filter_type, scanline, previous): + """Undo the filter for a scanline. `scanline` is a sequence of + bytes that does not include the initial filter type byte. + `previous` is decoded previous scanline (for straightlaced + images this is the previous pixel row, but for interlaced + images, it is the previous scanline in the reduced image, which + in general is not the previous pixel row in the final image). + When there is no previous scanline (the first row of a + straightlaced image, or the first row in one of the passes in an + interlaced image), then this argument should be ``None``. + + The scanline will have the effects of filtering removed, and the + result will be returned as a fresh sequence of bytes. + """ + + # :todo: Would it be better to update scanline in place? + # Yes, with the Cython extension making the undo_filter fast, + # updating scanline inplace makes the code 3 times faster + # (reading 50 images of 800x800 went from 40s to 16s) + result = scanline + + if filter_type == 0: + return result + + if filter_type not in (1,2,3,4): + raise FormatError('Invalid PNG Filter Type.' + ' See http://www.w3.org/TR/2003/REC-PNG-20031110/#9Filters .') + + # Filter unit. The stride from one pixel to the corresponding + # byte from the previous pixel. Normally this is the pixel + # size in bytes, but when this is smaller than 1, the previous + # byte is used instead. + fu = max(1, self.psize) + + # For the first line of a pass, synthesize a dummy previous + # line. An alternative approach would be to observe that on the + # first line 'up' is the same as 'null', 'paeth' is the same + # as 'sub', with only 'average' requiring any special case. + if not previous: + previous = array('B', [0]*len(scanline)) + + def sub(): + """Undo sub filter.""" + + ai = 0 + # Loop starts at index fu. Observe that the initial part + # of the result is already filled in correctly with + # scanline. + for i in range(fu, len(result)): + x = scanline[i] + a = result[ai] + result[i] = (x + a) & 0xff + ai += 1 + + def up(): + """Undo up filter.""" + + for i in range(len(result)): + x = scanline[i] + b = previous[i] + result[i] = (x + b) & 0xff + + def average(): + """Undo average filter.""" + + ai = -fu + for i in range(len(result)): + x = scanline[i] + if ai < 0: + a = 0 + else: + a = result[ai] + b = previous[i] + result[i] = (x + ((a + b) >> 1)) & 0xff + ai += 1 + + def paeth(): + """Undo Paeth filter.""" + + # Also used for ci. + ai = -fu + for i in range(len(result)): + x = scanline[i] + if ai < 0: + a = c = 0 + else: + a = result[ai] + c = previous[ai] + b = previous[i] + p = a + b - c + pa = abs(p - a) + pb = abs(p - b) + pc = abs(p - c) + if pa <= pb and pa <= pc: + pr = a + elif pb <= pc: + pr = b + else: + pr = c + result[i] = (x + pr) & 0xff + ai += 1 + + # Call appropriate filter algorithm. Note that 0 has already + # been dealt with. + (None, + pngfilters.undo_filter_sub, + pngfilters.undo_filter_up, + pngfilters.undo_filter_average, + pngfilters.undo_filter_paeth)[filter_type](fu, scanline, previous, result) + return result + + def deinterlace(self, raw): + """ + Read raw pixel data, undo filters, deinterlace, and flatten. + Return in flat row flat pixel format. + """ + + # Values per row (of the target image) + vpr = self.width * self.planes + + # Make a result array, and make it big enough. Interleaving + # writes to the output array randomly (well, not quite), so the + # entire output array must be in memory. + fmt = 'BH'[self.bitdepth > 8] + a = array(fmt, [0]*vpr*self.height) + source_offset = 0 + + for xstart, ystart, xstep, ystep in _adam7: + if xstart >= self.width: + continue + # The previous (reconstructed) scanline. None at the + # beginning of a pass to indicate that there is no previous + # line. + recon = None + # Pixels per row (reduced pass image) + ppr = int(math.ceil((self.width-xstart)/float(xstep))) + # Row size in bytes for this pass. + row_size = int(math.ceil(self.psize * ppr)) + for y in range(ystart, self.height, ystep): + filter_type = raw[source_offset] + source_offset += 1 + scanline = raw[source_offset:source_offset+row_size] + source_offset += row_size + recon = self.undo_filter(filter_type, scanline, recon) + # Convert so that there is one element per pixel value + flat = self.serialtoflat(recon, ppr) + if xstep == 1: + assert xstart == 0 + offset = y * vpr + a[offset:offset+vpr] = flat + else: + offset = y * vpr + xstart * self.planes + end_offset = (y+1) * vpr + skip = self.planes * xstep + for i in range(self.planes): + a[offset+i:end_offset:skip] = \ + flat[i::self.planes] + return a + + def iterboxed(self, rows): + """Iterator that yields each scanline in boxed row flat pixel + format. `rows` should be an iterator that yields the bytes of + each row in turn. + """ + + def asvalues(raw): + """Convert a row of raw bytes into a flat row. Result will + be a freshly allocated object, not shared with + argument. + """ + + if self.bitdepth == 8: + return array('B', raw) + if self.bitdepth == 16: + raw = tostring(raw) + return array('H', struct.unpack('!%dH' % (len(raw)//2), raw)) + assert self.bitdepth < 8 + width = self.width + # Samples per byte + spb = 8//self.bitdepth + out = array('B') + mask = 2**self.bitdepth - 1 + shifts = [self.bitdepth * i + for i in reversed(list(range(spb)))] + for o in raw: + out.extend([mask&(o>>i) for i in shifts]) + return out[:width] + + return map(asvalues, rows) + + def serialtoflat(self, bytes, width=None): + """Convert serial format (byte stream) pixel data to flat row + flat pixel. + """ + + if self.bitdepth == 8: + return bytes + if self.bitdepth == 16: + bytes = tostring(bytes) + return array('H', + struct.unpack('!%dH' % (len(bytes)//2), bytes)) + assert self.bitdepth < 8 + if width is None: + width = self.width + # Samples per byte + spb = 8//self.bitdepth + out = array('B') + mask = 2**self.bitdepth - 1 + shifts = list(map(self.bitdepth.__mul__, reversed(list(range(spb))))) + l = width + for o in bytes: + out.extend([(mask&(o>>s)) for s in shifts][:l]) + l -= spb + if l <= 0: + l = width + return out + + def iterstraight(self, raw): + """Iterator that undoes the effect of filtering, and yields + each row in serialised format (as a sequence of bytes). + Assumes input is straightlaced. `raw` should be an iterable + that yields the raw bytes in chunks of arbitrary size. + """ + + # length of row, in bytes + rb = self.row_bytes + a = array('B') + # The previous (reconstructed) scanline. None indicates first + # line of image. + recon = None + for some in raw: + a.extend(some) + while len(a) >= rb + 1: + filter_type = a[0] + scanline = a[1:rb+1] + del a[:rb+1] + recon = self.undo_filter(filter_type, scanline, recon) + yield recon + if len(a) != 0: + # :file:format We get here with a file format error: + # when the available bytes (after decompressing) do not + # pack into exact rows. + raise FormatError( + 'Wrong size for decompressed IDAT chunk.') + assert len(a) == 0 + + def validate_signature(self): + """If signature (header) has not been read then read and + validate it; otherwise do nothing. + """ + + if self.signature: + return + self.signature = self.file.read(8) + if self.signature != _signature: + raise FormatError("PNG file has invalid signature.") + + def preamble(self, lenient=False): + """ + Extract the image metadata by reading the initial part of + the PNG file up to the start of the ``IDAT`` chunk. All the + chunks that precede the ``IDAT`` chunk are read and either + processed for metadata or discarded. + + If the optional `lenient` argument evaluates to `True`, checksum + failures will raise warnings rather than exceptions. + """ + + self.validate_signature() + + while True: + if not self.atchunk: + self.atchunk = self.chunklentype() + if self.atchunk is None: + raise FormatError( + 'This PNG file has no IDAT chunks.') + if self.atchunk[1] == b'IDAT': + return + self.process_chunk(lenient=lenient) + + def chunklentype(self): + """Reads just enough of the input to determine the next + chunk's length and type, returned as a (*length*, *type*) pair + where *type* is a string. If there are no more chunks, ``None`` + is returned. + """ + + x = self.file.read(8) + if not x: + return None + if len(x) != 8: + raise FormatError( + 'End of file whilst reading chunk length and type.') + length,type = struct.unpack('!I4s', x) + if length > 2**31-1: + raise FormatError('Chunk %s is too large: %d.' % (type,length)) + return length,type + + def process_chunk(self, lenient=False): + """Process the next chunk and its data. This only processes the + following chunk types, all others are ignored: ``IHDR``, + ``PLTE``, ``bKGD``, ``tRNS``, ``gAMA``, ``sBIT``, ``pHYs``. + + If the optional `lenient` argument evaluates to `True`, + checksum failures will raise warnings rather than exceptions. + """ + + type, data = self.chunk(lenient=lenient) + method = '_process_' + as_str(type) + m = getattr(self, method, None) + if m: + m(data) + + def _process_IHDR(self, data): + # http://www.w3.org/TR/PNG/#11IHDR + if len(data) != 13: + raise FormatError('IHDR chunk has incorrect length.') + (self.width, self.height, self.bitdepth, self.color_type, + self.compression, self.filter, + self.interlace) = struct.unpack("!2I5B", data) + + check_bitdepth_colortype(self.bitdepth, self.color_type) + + if self.compression != 0: + raise Error("unknown compression method %d" % self.compression) + if self.filter != 0: + raise FormatError("Unknown filter method %d," + " see http://www.w3.org/TR/2003/REC-PNG-20031110/#9Filters ." + % self.filter) + if self.interlace not in (0,1): + raise FormatError("Unknown interlace method %d," + " see http://www.w3.org/TR/2003/REC-PNG-20031110/#8InterlaceMethods ." + % self.interlace) + + # Derived values + # http://www.w3.org/TR/PNG/#6Colour-values + colormap = bool(self.color_type & 1) + greyscale = not (self.color_type & 2) + alpha = bool(self.color_type & 4) + color_planes = (3,1)[greyscale or colormap] + planes = color_planes + alpha + + self.colormap = colormap + self.greyscale = greyscale + self.alpha = alpha + self.color_planes = color_planes + self.planes = planes + self.psize = float(self.bitdepth)/float(8) * planes + if int(self.psize) == self.psize: + self.psize = int(self.psize) + self.row_bytes = int(math.ceil(self.width * self.psize)) + # Stores PLTE chunk if present, and is used to check + # chunk ordering constraints. + self.plte = None + # Stores tRNS chunk if present, and is used to check chunk + # ordering constraints. + self.trns = None + # Stores sbit chunk if present. + self.sbit = None + + def _process_PLTE(self, data): + # http://www.w3.org/TR/PNG/#11PLTE + if self.plte: + warnings.warn("Multiple PLTE chunks present.") + self.plte = data + if len(data) % 3 != 0: + raise FormatError( + "PLTE chunk's length should be a multiple of 3.") + if len(data) > (2**self.bitdepth)*3: + raise FormatError("PLTE chunk is too long.") + if len(data) == 0: + raise FormatError("Empty PLTE is not allowed.") + + def _process_bKGD(self, data): + try: + if self.colormap: + if not self.plte: + warnings.warn( + "PLTE chunk is required before bKGD chunk.") + self.background = struct.unpack('B', data) + else: + self.background = struct.unpack("!%dH" % self.color_planes, + data) + except struct.error: + raise FormatError("bKGD chunk has incorrect length.") + + def _process_tRNS(self, data): + # http://www.w3.org/TR/PNG/#11tRNS + self.trns = data + if self.colormap: + if not self.plte: + warnings.warn("PLTE chunk is required before tRNS chunk.") + else: + if len(data) > len(self.plte)/3: + # Was warning, but promoted to Error as it + # would otherwise cause pain later on. + raise FormatError("tRNS chunk is too long.") + else: + if self.alpha: + raise FormatError( + "tRNS chunk is not valid with colour type %d." % + self.color_type) + try: + self.transparent = \ + struct.unpack("!%dH" % self.color_planes, data) + except struct.error: + raise FormatError("tRNS chunk has incorrect length.") + + def _process_gAMA(self, data): + try: + self.gamma = struct.unpack("!L", data)[0] / 100000.0 + except struct.error: + raise FormatError("gAMA chunk has incorrect length.") + + def _process_sBIT(self, data): + self.sbit = data + if (self.colormap and len(data) != 3 or + not self.colormap and len(data) != self.planes): + raise FormatError("sBIT chunk has incorrect length.") + + def _process_pHYs(self, data): + # http://www.w3.org/TR/PNG/#11pHYs + self.phys = data + fmt = "!LLB" + if len(data) != struct.calcsize(fmt): + raise FormatError("pHYs chunk has incorrect length.") + self.x_pixels_per_unit, self.y_pixels_per_unit, unit = struct.unpack(fmt,data) + self.unit_is_meter = bool(unit) + + def read(self, lenient=False): + """ + Read the PNG file and decode it. Returns (`width`, `height`, + `pixels`, `metadata`). + + May use excessive memory. + + `pixels` are returned in boxed row flat pixel format. + + If the optional `lenient` argument evaluates to True, + checksum failures will raise warnings rather than exceptions. + """ + + def iteridat(): + """Iterator that yields all the ``IDAT`` chunks as strings.""" + while True: + try: + type, data = self.chunk(lenient=lenient) + except ValueError as e: + raise ChunkError(e.args[0]) + if type == b'IEND': + # http://www.w3.org/TR/PNG/#11IEND + break + if type != b'IDAT': + continue + # type == b'IDAT' + # http://www.w3.org/TR/PNG/#11IDAT + if self.colormap and not self.plte: + warnings.warn("PLTE chunk is required before IDAT chunk") + yield data + + def iterdecomp(idat): + """Iterator that yields decompressed strings. `idat` should + be an iterator that yields the ``IDAT`` chunk data. + """ + + # Currently, with no max_length parameter to decompress, + # this routine will do one yield per IDAT chunk: Not very + # incremental. + d = zlib.decompressobj() + # Each IDAT chunk is passed to the decompressor, then any + # remaining state is decompressed out. + for data in idat: + # :todo: add a max_length argument here to limit output + # size. + yield array('B', d.decompress(data)) + yield array('B', d.flush()) + + self.preamble(lenient=lenient) + raw = iterdecomp(iteridat()) + + if self.interlace: + raw = array('B', itertools.chain(*raw)) + arraycode = 'BH'[self.bitdepth>8] + # Like :meth:`group` but producing an array.array object for + # each row. + pixels = map(lambda *row: array(arraycode, row), + *[iter(self.deinterlace(raw))]*self.width*self.planes) + else: + pixels = self.iterboxed(self.iterstraight(raw)) + meta = dict() + for attr in 'greyscale alpha planes bitdepth interlace'.split(): + meta[attr] = getattr(self, attr) + meta['size'] = (self.width, self.height) + for attr in 'gamma transparent background'.split(): + a = getattr(self, attr, None) + if a is not None: + meta[attr] = a + if self.plte: + meta['palette'] = self.palette() + return self.width, self.height, pixels, meta + + + def read_flat(self): + """ + Read a PNG file and decode it into flat row flat pixel format. + Returns (*width*, *height*, *pixels*, *metadata*). + + May use excessive memory. + + `pixels` are returned in flat row flat pixel format. + + See also the :meth:`read` method which returns pixels in the + more stream-friendly boxed row flat pixel format. + """ + + x, y, pixel, meta = self.read() + arraycode = 'BH'[meta['bitdepth']>8] + pixel = array(arraycode, itertools.chain(*pixel)) + return x, y, pixel, meta + + def palette(self, alpha='natural'): + """Returns a palette that is a sequence of 3-tuples or 4-tuples, + synthesizing it from the ``PLTE`` and ``tRNS`` chunks. These + chunks should have already been processed (for example, by + calling the :meth:`preamble` method). All the tuples are the + same size: 3-tuples if there is no ``tRNS`` chunk, 4-tuples when + there is a ``tRNS`` chunk. Assumes that the image is colour type + 3 and therefore a ``PLTE`` chunk is required. + + If the `alpha` argument is ``'force'`` then an alpha channel is + always added, forcing the result to be a sequence of 4-tuples. + """ + + if not self.plte: + raise FormatError( + "Required PLTE chunk is missing in colour type 3 image.") + plte = group(array('B', self.plte), 3) + if self.trns or alpha == 'force': + trns = array('B', self.trns or []) + trns.extend([255]*(len(plte)-len(trns))) + plte = list(map(operator.add, plte, group(trns, 1))) + return plte + + def asDirect(self): + """Returns the image data as a direct representation of an + ``x * y * planes`` array. This method is intended to remove the + need for callers to deal with palettes and transparency + themselves. Images with a palette (colour type 3) + are converted to RGB or RGBA; images with transparency (a + ``tRNS`` chunk) are converted to LA or RGBA as appropriate. + When returned in this format the pixel values represent the + colour value directly without needing to refer to palettes or + transparency information. + + Like the :meth:`read` method this method returns a 4-tuple: + + (*width*, *height*, *pixels*, *meta*) + + This method normally returns pixel values with the bit depth + they have in the source image, but when the source PNG has an + ``sBIT`` chunk it is inspected and can reduce the bit depth of + the result pixels; pixel values will be reduced according to + the bit depth specified in the ``sBIT`` chunk (PNG nerds should + note a single result bit depth is used for all channels; the + maximum of the ones specified in the ``sBIT`` chunk. An RGB565 + image will be rescaled to 6-bit RGB666). + + The *meta* dictionary that is returned reflects the `direct` + format and not the original source image. For example, an RGB + source image with a ``tRNS`` chunk to represent a transparent + colour, will have ``planes=3`` and ``alpha=False`` for the + source image, but the *meta* dictionary returned by this method + will have ``planes=4`` and ``alpha=True`` because an alpha + channel is synthesized and added. + + *pixels* is the pixel data in boxed row flat pixel format (just + like the :meth:`read` method). + + All the other aspects of the image data are not changed. + """ + + self.preamble() + + # Simple case, no conversion necessary. + if not self.colormap and not self.trns and not self.sbit: + return self.read() + + x,y,pixels,meta = self.read() + + if self.colormap: + meta['colormap'] = False + meta['alpha'] = bool(self.trns) + meta['bitdepth'] = 8 + meta['planes'] = 3 + bool(self.trns) + plte = self.palette() + def iterpal(pixels): + for row in pixels: + row = [plte[x] for x in row] + yield array('B', itertools.chain(*row)) + pixels = iterpal(pixels) + elif self.trns: + # It would be nice if there was some reasonable way + # of doing this without generating a whole load of + # intermediate tuples. But tuples does seem like the + # easiest way, with no other way clearly much simpler or + # much faster. (Actually, the L to LA conversion could + # perhaps go faster (all those 1-tuples!), but I still + # wonder whether the code proliferation is worth it) + it = self.transparent + maxval = 2**meta['bitdepth']-1 + planes = meta['planes'] + meta['alpha'] = True + meta['planes'] += 1 + typecode = 'BH'[meta['bitdepth']>8] + def itertrns(pixels): + for row in pixels: + # For each row we group it into pixels, then form a + # characterisation vector that says whether each + # pixel is opaque or not. Then we convert + # True/False to 0/maxval (by multiplication), + # and add it as the extra channel. + row = group(row, planes) + opa = map(it.__ne__, row) + opa = map(maxval.__mul__, opa) + opa = list(zip(opa)) # convert to 1-tuples + yield array(typecode, + itertools.chain(*map(operator.add, row, opa))) + pixels = itertrns(pixels) + targetbitdepth = None + if self.sbit: + sbit = struct.unpack('%dB' % len(self.sbit), self.sbit) + targetbitdepth = max(sbit) + if targetbitdepth > meta['bitdepth']: + raise Error('sBIT chunk %r exceeds bitdepth %d' % + (sbit,self.bitdepth)) + if min(sbit) <= 0: + raise Error('sBIT chunk %r has a 0-entry' % sbit) + if targetbitdepth == meta['bitdepth']: + targetbitdepth = None + if targetbitdepth: + shift = meta['bitdepth'] - targetbitdepth + meta['bitdepth'] = targetbitdepth + def itershift(pixels): + for row in pixels: + yield [p >> shift for p in row] + pixels = itershift(pixels) + return x,y,pixels,meta + + def asFloat(self, maxval=1.0): + """Return image pixels as per :meth:`asDirect` method, but scale + all pixel values to be floating point values between 0.0 and + *maxval*. + """ + + x,y,pixels,info = self.asDirect() + sourcemaxval = 2**info['bitdepth']-1 + del info['bitdepth'] + info['maxval'] = float(maxval) + factor = float(maxval)/float(sourcemaxval) + def iterfloat(): + for row in pixels: + yield [factor * p for p in row] + return x,y,iterfloat(),info + + def _as_rescale(self, get, targetbitdepth): + """Helper used by :meth:`asRGB8` and :meth:`asRGBA8`.""" + + width,height,pixels,meta = get() + maxval = 2**meta['bitdepth'] - 1 + targetmaxval = 2**targetbitdepth - 1 + factor = float(targetmaxval) / float(maxval) + meta['bitdepth'] = targetbitdepth + def iterscale(): + for row in pixels: + yield [int(round(x*factor)) for x in row] + if maxval == targetmaxval: + return width, height, pixels, meta + else: + return width, height, iterscale(), meta + + def asRGB8(self): + """Return the image data as an RGB pixels with 8-bits per + sample. This is like the :meth:`asRGB` method except that + this method additionally rescales the values so that they + are all between 0 and 255 (8-bit). In the case where the + source image has a bit depth < 8 the transformation preserves + all the information; where the source image has bit depth + > 8, then rescaling to 8-bit values loses precision. No + dithering is performed. Like :meth:`asRGB`, an alpha channel + in the source image will raise an exception. + + This function returns a 4-tuple: + (*width*, *height*, *pixels*, *metadata*). + *width*, *height*, *metadata* are as per the + :meth:`read` method. + + *pixels* is the pixel data in boxed row flat pixel format. + """ + + return self._as_rescale(self.asRGB, 8) + + def asRGBA8(self): + """Return the image data as RGBA pixels with 8-bits per + sample. This method is similar to :meth:`asRGB8` and + :meth:`asRGBA`: The result pixels have an alpha channel, *and* + values are rescaled to the range 0 to 255. The alpha channel is + synthesized if necessary (with a small speed penalty). + """ + + return self._as_rescale(self.asRGBA, 8) + + def asRGB(self): + """Return image as RGB pixels. RGB colour images are passed + through unchanged; greyscales are expanded into RGB + triplets (there is a small speed overhead for doing this). + + An alpha channel in the source image will raise an + exception. + + The return values are as for the :meth:`read` method + except that the *metadata* reflect the returned pixels, not the + source image. In particular, for this method + ``metadata['greyscale']`` will be ``False``. + """ + + width,height,pixels,meta = self.asDirect() + if meta['alpha']: + raise Error("will not convert image with alpha channel to RGB") + if not meta['greyscale']: + return width,height,pixels,meta + meta['greyscale'] = False + typecode = 'BH'[meta['bitdepth'] > 8] + def iterrgb(): + for row in pixels: + a = array(typecode, [0]) * 3 * width + for i in range(3): + a[i::3] = row + yield a + return width,height,iterrgb(),meta + + def asRGBA(self): + """Return image as RGBA pixels. Greyscales are expanded into + RGB triplets; an alpha channel is synthesized if necessary. + The return values are as for the :meth:`read` method + except that the *metadata* reflect the returned pixels, not the + source image. In particular, for this method + ``metadata['greyscale']`` will be ``False``, and + ``metadata['alpha']`` will be ``True``. + """ + + width,height,pixels,meta = self.asDirect() + if meta['alpha'] and not meta['greyscale']: + return width,height,pixels,meta + typecode = 'BH'[meta['bitdepth'] > 8] + maxval = 2**meta['bitdepth'] - 1 + maxbuffer = struct.pack('=' + typecode, maxval) * 4 * width + def newarray(): + return array(typecode, maxbuffer) + + if meta['alpha'] and meta['greyscale']: + # LA to RGBA + def convert(): + for row in pixels: + # Create a fresh target row, then copy L channel + # into first three target channels, and A channel + # into fourth channel. + a = newarray() + pngfilters.convert_la_to_rgba(row, a) + yield a + elif meta['greyscale']: + # L to RGBA + def convert(): + for row in pixels: + a = newarray() + pngfilters.convert_l_to_rgba(row, a) + yield a + else: + assert not meta['alpha'] and not meta['greyscale'] + # RGB to RGBA + def convert(): + for row in pixels: + a = newarray() + pngfilters.convert_rgb_to_rgba(row, a) + yield a + meta['alpha'] = True + meta['greyscale'] = False + return width,height,convert(),meta + +def check_bitdepth_colortype(bitdepth, colortype): + """Check that `bitdepth` and `colortype` are both valid, + and specified in a valid combination. Returns if valid, + raise an Exception if not valid. + """ + + if bitdepth not in (1,2,4,8,16): + raise FormatError("invalid bit depth %d" % bitdepth) + if colortype not in (0,2,3,4,6): + raise FormatError("invalid colour type %d" % colortype) + # Check indexed (palettized) images have 8 or fewer bits + # per pixel; check only indexed or greyscale images have + # fewer than 8 bits per pixel. + if colortype & 1 and bitdepth > 8: + raise FormatError( + "Indexed images (colour type %d) cannot" + " have bitdepth > 8 (bit depth %d)." + " See http://www.w3.org/TR/2003/REC-PNG-20031110/#table111 ." + % (bitdepth, colortype)) + if bitdepth < 8 and colortype not in (0,3): + raise FormatError("Illegal combination of bit depth (%d)" + " and colour type (%d)." + " See http://www.w3.org/TR/2003/REC-PNG-20031110/#table111 ." + % (bitdepth, colortype)) + +def isinteger(x): + try: + return int(x) == x + except (TypeError, ValueError): + return False + + +# === Support for users without Cython === + +try: + pngfilters +except NameError: + class pngfilters(object): + def undo_filter_sub(filter_unit, scanline, previous, result): + """Undo sub filter.""" + + ai = 0 + # Loops starts at index fu. Observe that the initial part + # of the result is already filled in correctly with + # scanline. + for i in range(filter_unit, len(result)): + x = scanline[i] + a = result[ai] + result[i] = (x + a) & 0xff + ai += 1 + undo_filter_sub = staticmethod(undo_filter_sub) + + def undo_filter_up(filter_unit, scanline, previous, result): + """Undo up filter.""" + + for i in range(len(result)): + x = scanline[i] + b = previous[i] + result[i] = (x + b) & 0xff + undo_filter_up = staticmethod(undo_filter_up) + + def undo_filter_average(filter_unit, scanline, previous, result): + """Undo up filter.""" + + ai = -filter_unit + for i in range(len(result)): + x = scanline[i] + if ai < 0: + a = 0 + else: + a = result[ai] + b = previous[i] + result[i] = (x + ((a + b) >> 1)) & 0xff + ai += 1 + undo_filter_average = staticmethod(undo_filter_average) + + def undo_filter_paeth(filter_unit, scanline, previous, result): + """Undo Paeth filter.""" + + # Also used for ci. + ai = -filter_unit + for i in range(len(result)): + x = scanline[i] + if ai < 0: + a = c = 0 + else: + a = result[ai] + c = previous[ai] + b = previous[i] + p = a + b - c + pa = abs(p - a) + pb = abs(p - b) + pc = abs(p - c) + if pa <= pb and pa <= pc: + pr = a + elif pb <= pc: + pr = b + else: + pr = c + result[i] = (x + pr) & 0xff + ai += 1 + undo_filter_paeth = staticmethod(undo_filter_paeth) + + def convert_la_to_rgba(row, result): + for i in range(3): + result[i::4] = row[0::2] + result[3::4] = row[1::2] + convert_la_to_rgba = staticmethod(convert_la_to_rgba) + + def convert_l_to_rgba(row, result): + """Convert a grayscale image to RGBA. This method assumes + the alpha channel in result is already correctly + initialized. + """ + for i in range(3): + result[i::4] = row + convert_l_to_rgba = staticmethod(convert_l_to_rgba) + + def convert_rgb_to_rgba(row, result): + """Convert an RGB image to RGBA. This method assumes the + alpha channel in result is already correctly initialized. + """ + for i in range(3): + result[i::4] = row[i::3] + convert_rgb_to_rgba = staticmethod(convert_rgb_to_rgba) + + +# === Command Line Support === + +def read_pam_header(infile): + """ + Read (the rest of a) PAM header. `infile` should be positioned + immediately after the initial 'P7' line (at the beginning of the + second line). Returns are as for `read_pnm_header`. + """ + + # Unlike PBM, PGM, and PPM, we can read the header a line at a time. + header = dict() + while True: + l = infile.readline().strip() + if l == b'ENDHDR': + break + if not l: + raise EOFError('PAM ended prematurely') + if l[0] == b'#': + continue + l = l.split(None, 1) + if l[0] not in header: + header[l[0]] = l[1] + else: + header[l[0]] += b' ' + l[1] + + required = [b'WIDTH', b'HEIGHT', b'DEPTH', b'MAXVAL'] + WIDTH,HEIGHT,DEPTH,MAXVAL = required + present = [x for x in required if x in header] + if len(present) != len(required): + raise Error('PAM file must specify WIDTH, HEIGHT, DEPTH, and MAXVAL') + width = int(header[WIDTH]) + height = int(header[HEIGHT]) + depth = int(header[DEPTH]) + maxval = int(header[MAXVAL]) + if (width <= 0 or + height <= 0 or + depth <= 0 or + maxval <= 0): + raise Error( + 'WIDTH, HEIGHT, DEPTH, MAXVAL must all be positive integers') + return 'P7', width, height, depth, maxval + +def read_pnm_header(infile, supported=(b'P5', b'P6')): + """ + Read a PNM header, returning (format,width,height,depth,maxval). + `width` and `height` are in pixels. `depth` is the number of + channels in the image; for PBM and PGM it is synthesized as 1, for + PPM as 3; for PAM images it is read from the header. `maxval` is + synthesized (as 1) for PBM images. + """ + + # Generally, see http://netpbm.sourceforge.net/doc/ppm.html + # and http://netpbm.sourceforge.net/doc/pam.html + + # Technically 'P7' must be followed by a newline, so by using + # rstrip() we are being liberal in what we accept. I think this + # is acceptable. + type = infile.read(3).rstrip() + if type not in supported: + raise NotImplementedError('file format %s not supported' % type) + if type == b'P7': + # PAM header parsing is completely different. + return read_pam_header(infile) + # Expected number of tokens in header (3 for P4, 4 for P6) + expected = 4 + pbm = (b'P1', b'P4') + if type in pbm: + expected = 3 + header = [type] + + # We have to read the rest of the header byte by byte because the + # final whitespace character (immediately following the MAXVAL in + # the case of P6) may not be a newline. Of course all PNM files in + # the wild use a newline at this point, so it's tempting to use + # readline; but it would be wrong. + def getc(): + c = infile.read(1) + if not c: + raise Error('premature EOF reading PNM header') + return c + + c = getc() + while True: + # Skip whitespace that precedes a token. + while c.isspace(): + c = getc() + # Skip comments. + while c == '#': + while c not in b'\n\r': + c = getc() + if not c.isdigit(): + raise Error('unexpected character %s found in header' % c) + # According to the specification it is legal to have comments + # that appear in the middle of a token. + # This is bonkers; I've never seen it; and it's a bit awkward to + # code good lexers in Python (no goto). So we break on such + # cases. + token = b'' + while c.isdigit(): + token += c + c = getc() + # Slight hack. All "tokens" are decimal integers, so convert + # them here. + header.append(int(token)) + if len(header) == expected: + break + # Skip comments (again) + while c == '#': + while c not in '\n\r': + c = getc() + if not c.isspace(): + raise Error('expected header to end with whitespace, not %s' % c) + + if type in pbm: + # synthesize a MAXVAL + header.append(1) + depth = (1,3)[type == b'P6'] + return header[0], header[1], header[2], depth, header[3] + +def write_pnm(file, width, height, pixels, meta): + """Write a Netpbm PNM/PAM file. + """ + + bitdepth = meta['bitdepth'] + maxval = 2**bitdepth - 1 + # Rudely, the number of image planes can be used to determine + # whether we are L (PGM), LA (PAM), RGB (PPM), or RGBA (PAM). + planes = meta['planes'] + # Can be an assert as long as we assume that pixels and meta came + # from a PNG file. + assert planes in (1,2,3,4) + if planes in (1,3): + if 1 == planes: + # PGM + # Could generate PBM if maxval is 1, but we don't (for one + # thing, we'd have to convert the data, not just blat it + # out). + fmt = 'P5' + else: + # PPM + fmt = 'P6' + header = '%s %d %d %d\n' % (fmt, width, height, maxval) + if planes in (2,4): + # PAM + # See http://netpbm.sourceforge.net/doc/pam.html + if 2 == planes: + tupltype = 'GRAYSCALE_ALPHA' + else: + tupltype = 'RGB_ALPHA' + header = ('P7\nWIDTH %d\nHEIGHT %d\nDEPTH %d\nMAXVAL %d\n' + 'TUPLTYPE %s\nENDHDR\n' % + (width, height, planes, maxval, tupltype)) + file.write(header.encode('ascii')) + # Values per row + vpr = planes * width + # struct format + fmt = '>%d' % vpr + if maxval > 0xff: + fmt = fmt + 'H' + else: + fmt = fmt + 'B' + for row in pixels: + file.write(struct.pack(fmt, *row)) + file.flush() + +def color_triple(color): + """ + Convert a command line colour value to a RGB triple of integers. + FIXME: Somewhere we need support for greyscale backgrounds etc. + """ + if color.startswith('#') and len(color) == 4: + return (int(color[1], 16), + int(color[2], 16), + int(color[3], 16)) + if color.startswith('#') and len(color) == 7: + return (int(color[1:3], 16), + int(color[3:5], 16), + int(color[5:7], 16)) + elif color.startswith('#') and len(color) == 13: + return (int(color[1:5], 16), + int(color[5:9], 16), + int(color[9:13], 16)) + +def _add_common_options(parser): + """Call *parser.add_option* for each of the options that are + common between this PNG--PNM conversion tool and the gen + tool. + """ + parser.add_option("-i", "--interlace", + default=False, action="store_true", + help="create an interlaced PNG file (Adam7)") + parser.add_option("-t", "--transparent", + action="store", type="string", metavar="#RRGGBB", + help="mark the specified colour as transparent") + parser.add_option("-b", "--background", + action="store", type="string", metavar="#RRGGBB", + help="save the specified background colour") + parser.add_option("-g", "--gamma", + action="store", type="float", metavar="value", + help="save the specified gamma value") + parser.add_option("-c", "--compression", + action="store", type="int", metavar="level", + help="zlib compression level (0-9)") + return parser + +def _main(argv): + """ + Run the PNG encoder with options from the command line. + """ + + # Parse command line arguments + from optparse import OptionParser + version = '%prog ' + __version__ + parser = OptionParser(version=version) + parser.set_usage("%prog [options] [imagefile]") + parser.add_option('-r', '--read-png', default=False, + action='store_true', + help='Read PNG, write PNM') + parser.add_option("-a", "--alpha", + action="store", type="string", metavar="pgmfile", + help="alpha channel transparency (RGBA)") + _add_common_options(parser) + + (options, args) = parser.parse_args(args=argv[1:]) + + # Convert options + if options.transparent is not None: + options.transparent = color_triple(options.transparent) + if options.background is not None: + options.background = color_triple(options.background) + + # Prepare input and output files + if len(args) == 0: + infilename = '-' + infile = sys.stdin + elif len(args) == 1: + infilename = args[0] + infile = open(infilename, 'rb') + else: + parser.error("more than one input file") + outfile = sys.stdout + if sys.platform == "win32": + import msvcrt, os + msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) + + if options.read_png: + # Encode PNG to PPM + png = Reader(file=infile) + width,height,pixels,meta = png.asDirect() + write_pnm(outfile, width, height, pixels, meta) + else: + # Encode PNM to PNG + format, width, height, depth, maxval = \ + read_pnm_header(infile, (b'P5',b'P6',b'P7')) + # When it comes to the variety of input formats, we do something + # rather rude. Observe that L, LA, RGB, RGBA are the 4 colour + # types supported by PNG and that they correspond to 1, 2, 3, 4 + # channels respectively. So we use the number of channels in + # the source image to determine which one we have. We do not + # care about TUPLTYPE. + greyscale = depth <= 2 + pamalpha = depth in (2,4) + supported = [2**x-1 for x in range(1,17)] + try: + mi = supported.index(maxval) + except ValueError: + raise NotImplementedError( + 'your maxval (%s) not in supported list %s' % + (maxval, str(supported))) + bitdepth = mi+1 + writer = Writer(width, height, + greyscale=greyscale, + bitdepth=bitdepth, + interlace=options.interlace, + transparent=options.transparent, + background=options.background, + alpha=bool(pamalpha or options.alpha), + gamma=options.gamma, + compression=options.compression) + if options.alpha: + pgmfile = open(options.alpha, 'rb') + format, awidth, aheight, adepth, amaxval = \ + read_pnm_header(pgmfile, 'P5') + if amaxval != '255': + raise NotImplementedError( + 'maxval %s not supported for alpha channel' % amaxval) + if (awidth, aheight) != (width, height): + raise ValueError("alpha channel image size mismatch" + " (%s has %sx%s but %s has %sx%s)" + % (infilename, width, height, + options.alpha, awidth, aheight)) + writer.convert_ppm_and_pgm(infile, pgmfile, outfile) + else: + writer.convert_pnm(infile, outfile) + + +if __name__ == '__main__': + try: + _main(sys.argv) + except Error as e: + print(e, file=sys.stderr) diff --git a/Esami/2022-2023/Esame-7/program.andrea.py b/Esami/2022-2023/Esame-7/program.andrea.py new file mode 100644 index 0000000..81b9ec4 --- /dev/null +++ b/Esami/2022-2023/Esame-7/program.andrea.py @@ -0,0 +1,384 @@ + + + + +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +################################################################################ +################################################################################ +################################################################################ + +""" Operazioni da fare PRIMA DI TUTTO: + 1) Salvare il file come program.py + 2) Assegnare le variabili sottostanti con il tuo + NOME, COGNOME, NUMERO DI MATRICOLA + +Per superare l'esame è necessario: + - !!!riempire le informazioni personali nelle variabili qui sotto!!! + - AND risolvere almeno 1 esercizio di tipo ex (problema ricorsivo) + - AND risolvere almeno 3 esercizi di tipo func + - AND ottenere un punteggio maggiore o uguale a 18 + +Il punteggio finale è la somma dei punteggi dei problemi risolti. +""" +nome = "Andrea" +cognome = "Sterbini" +matricola = "42" + +################################################################################ +################################################################################ +################################################################################ +# ---------------------------- DEBUG SUGGESTIONS ----------------------------- # +# To run only some of the tests, you can comment the entries with which the +# 'tests' list is assigned at the end of grade.py +# +# To debug recursive functions you can turn off the recursive test setting +# DEBUG=True in the file grade.py +# +# DEBUG=True turns on also the STACK TRACE that allows you to know which +# line number in program.py generated the error. +################################################################################ + +# ---------------------------- FUNC 1 ---------------------------- # + +''' +Func 1: 2 punti +Si definisca la funzione func1(diz1, diz2) che riceve come argomenti +due dizionari che hanno chiavi intere e valori liste di stringhe. La +funzione deve tornare il dizionario che contiene le sole chiavi in +comune ad entrambi i dizionari. I valori associati a ciascuna chiave +sono quelli che appaiono in una sola delle due liste associate a +quella chiave nei due dizionari. Questi valori, senza ripetizioni, +vanno ordinati in ordine di lunghezza decrescente ed in caso di parità +in ordine alfabetico crescente. + +Esempio: +diz1 = { 1: ['a', 'bc', 'a'], 2: ['b', 'cr', 'e'], 3: ['a', 'qrt', 'st'] } +diz2 = { 1: ['a', 'cd', 'f'], 5: ['b', 'cr', 'e'], 3: ['a', 'bn', 'c'] } +il risultato sarà { 1: ['bc', 'cd', 'f'], 3: ['qrt', 'bn', 'st', 'c'] } +''' + +def func1(diz1, diz2): + pass + def criterio(s): return -len(s), s + return { + k : sorted(set(v1).symmetric_difference(set(diz2[k])), key=criterio) + for k,v1 in diz1.items() + if k in diz2 + } + +#diz1 = { 1: ['a', 'bc', 'a'], 2: ['b', 'cr', 'e'], 3: ['a', 'qrt', 'st'] } +#diz2 = { 1: ['a', 'cd', 'f'], 5: ['b', 'cr', 'e'], 3: ['a', 'bn', 'c'] } +#print(func1(diz1,diz2)) + +# ---------------------------- FUNC 2 ---------------------------- # + +''' +Func 2: 2 punti + +Si definisca la funzione func2(text) che riceve come argomento: +- text: una stringa formata da parole separate da spazi +e che ritorna un dizionario che ha: + - come chiavi la lettera iniziale delle parole presenti, minuscola + - come valore il numero di parole che contengono quella lettera + ignorando la differenza tra minuscole e maiuscole + +Esempio: +text = 'sOtto lA panca La caPra Canta Sopra LA Panca La CaPra crepa' +expected = { 's':2, 'l':4, 'p':6, 'c':6} +''' + +def func2(text): + pass + lower = text.lower().split() + diz = { parola[0]: 0 for parola in lower } + for parola in lower: + for carattere in diz: + if carattere in parola: + diz[carattere] += 1 + return diz + +#text = 'sOtto lA panca La caPra Canta Sopra LA Panca La CaPra crepa' +#print(func2(text)) + +# ---------------------------- FUNC 3 ---------------------------- # +''' +Func 3: 4 punti +Definite la funzione func3(textfile_in, textfile_out) che riceve come argomento: +- textfile_in: il percorso di un file di testo da leggere +- textfile_out: il percorso di un file di testo da creare + +Il file indicato da textfile_in contiene dei numeri +float oppure interi, positivi o negativi, separati da spazi. + +La funzione deve leggere i numeri, ordinarli in ordine decrescente di +di caratteri numerici presenti e in caso di parità in ordine crescente di valore. +Quindi deve scrivere questi numeri ordinati nel file textfile_out, +separati da virgola e spazio. Infine la funzione restituisce la +quantità di numeri letti da textfile_in. + +Esempio: +se il file textfile_in contiene la riga +-23.5 17 -141 +322.7 -3227 +Nel file textfile_out la funzione deve scrivere la riga +-3227, +322.7, -141, -23.5, 17 +e tornare il valore 5 +''' + +def func3(textfile_in, textfile_out): + pass + def criterio(elemento): + cifre = elemento.replace('.','') + cifre = cifre.replace('-', '') + cifre = cifre.replace('+', '') + return -len(cifre), float(elemento) + with open(textfile_in) as FIN: + numeri = FIN.read().split() + numeri.sort(key = criterio) + with open(textfile_out, mode='w') as FOUT: + print(*numeri, sep=', ', file=FOUT) + return len(numeri) + + +# ---------------------------- FUNC 4 ---------------------------- # + +''' +Func 4: 4 punti +Si definisca la funzione func5(filein) che riceve come argomento +- filein: un file di testo contenente una matrice di interi NxM + separati da spazi + +e che ritorna la matrice trasposta rispetto alla diagonale secondaria, +ovvero quella che va dall'elemento in alto a destra a quello in basso +a sinistra. La matrice da restituire e' rappresentata come lista di liste. + +Esempio: +se il file filein contiene la matrice +1 2 3 4 +5 6 7 8 +9 10 11 12 +la funzione dovrà tornare la matrice riflessa rispetto alla diagonale 4-9, +come lista di liste +[[12, 8, 4], + [11, 7, 3], + [10, 6, 2], + [ 9, 5, 1]] +''' +def func4(input_filename): + pass + with open(input_filename) as FIN: + M = [ list(map(int, riga.split())) for riga in FIN ] + W = len(M[0]) + H = len(M) + return [ [ M[H-1-y][W-1-x] + for y in range(H)] + for x in range(W)] + +# ---------------------------- FUNC 5 ---------------------------- # + +''' +Func 5: 8 punti +Si definisca la funzione func5(txt_input, width, height, png_output) che riceve come argomenti +- txt_input: il percorso di un file che contiene un elenco di figure da disegnare +- width: larghezza in pixel dell'immagine da creare +- height: altezza in pixel dell'immagine da creare +- png_output: il percorso di una immagine PNG che dovete creare, contenente le figure + +La funzione deve creare una immagine a sfondo nero e disegnarci sopra +tutte le figure indicate nel file 'txt_input', nell'ordine in cui +appaiono nel file. + +Il file txt_file contiene, una per riga, separate da spazi: +- una parola che indica il tipo di figura da disegnare +- le tre componenti R G B del colore da usare +- le coordinate e gli altri parametri necessari a definire la figura +Possono essre presenti 2 tipi di figura: +- diagonale discendente di un quadrato (in direzione -45°) + diagonalDOWN R G B x y L + La diagonale inizia nel punto (x,y), si dirige in BASSO a destra, ed è lunga L pixel +- diagonale ascendente di un quadrato (in direzione +45°) + diagonalUP R G B x y L + La diagonale inizia nel punto (x,y), si dirige in ALTO a destra, ed è lunga L pixel + +Quindi deve salvare l'immagine ottenuta nel file 'png_output' usando la funzione images.save. +Inoltre deve ritornare il numero di diagonali disegnate dei due tipi +come tupla dei due valori (DIAGUP,DIAGDOWN) + +NOTA: va gestito correttamente lo sbordare delle figure dalla +immagine, infatti sono ammesse anche coordinate negative, e dimensioni +o parametro L tali da far sbordare la figura dalla immagine + +Esempio: se il file func5/in_1.txt contiene le 3 righe +diagonalDOWN 0 255 0 -30 -40 110 +diagonalUP 255 0 0 20 100 200 +diagonalUP 0 0 255 10 120 50 + +l'esecuzione della funzione func5('func5/in_1.txt', 50, 100, 'func5/your_image_1.png') +produrrà una figura uguale al file 'func5/expected_1.png' +e tornerà la coppia (2, 1) +''' + + +import images +def func5(txt_input, width, height, png_output): + pass + def draw_diagonalDown(img, W, H, x, y, L, color): + for i in range(L): + X,Y = x+i, y+i + if 0 <= X < W and 0 <= Y < H: + img[Y][X] = color + def draw_diagonalUp(img, W, H, x, y, L, color): + for i in range(L): + X,Y = x+i, y-i + if 0 <= X < W and 0 <= Y < H: + img[Y][X] = color + img = [ [(0,0,0)]*width for _ in range(height) ] # immagine vuota, nera + diagUP = diagDOWN = 0 + with open(txt_input) as F: + for line in F: + tipo, *data= line.split() + R, G, B, x, y, L = list(map(int, data)) + if tipo == 'diagonalDOWN': + draw_diagonalDown(img, width, height, x, y, L, (R, G, B)) + diagDOWN += 1 + elif tipo == 'diagonalUP': + draw_diagonalUp( img, width, height, x, y, L, (R, G, B)) + diagUP += 1 + images.save(img, png_output) + return diagUP, diagDOWN + +# print(func5('func5/in_1.txt', 50, 100, 'func5/out_1.png')) + + +# ---------------------------- EX 1 ---------------------------- # + +''' +Esercizio 1 ricorsivo (6 punti): + +Si definisca la funzione es1(root, valori), ricorsiva o che usa funzioni ricorsive, +che riceve in input: +- la radice 'root' di un albero n-ario definito da nodi nary_tree.NaryTree +- una lista di interi 'valori' +che modifica distruttivamente l'albero 'root' sommando a tutti i nodi +che sono a profondità P il valore che nella lista 'valori' si trova +all'indice P, se esiste, altrimenti restano come sono. Si assuma che +la radice si trovi a profondità 0. + +La funzione deve restituire la somma 'total' di tutti i nodi +dell'albero risultante. + +Esempio: + values: [-42, -80, 68, 2, 81, 75, 54, 48, -4, 5] da sommare + root: -7 | -42 + / | | | \ | + -10 -3 -8 -10 -5 | -80 + / \ | | | | + 6 -2 9 7 -9 | +68 + + expected: -49 | + / | | | \ | + -90 -83 -88 -90 -85 | + / \ | | | | + 74 66 77 75 59 | + total = -134 + +ATTENZIONE: definite la funzione ricorsiva a livello esterno, +ovvero con la parola chiave 'def' appoggiata all'inizio della riga. +''' + +from nary_tree import NaryTree + +def ex1(root : NaryTree, valori : list[int]): + pass + return _ex1(root, valori, 0) + +def _ex1(root : NaryTree, valori : list[int], depth : int): + if depth < len(valori): + root.value += valori[depth] + total = root.value + for son in root.sons: + total += _ex1(son, valori, depth + 1) + return total + +# ---------------------------- EX 2 ---------------------------- # + +''' +Ex2: 3 + 3 points +Definite la funzione ex2(dirin, words), ricorsiva o che usa funzioni o metodi ricorsivi, +che riceve come argomenti: + - dirin: il path di una directory + - words: una lista di parole + +La funzione esplora dirin e tutte le sue sottodirectory (a tutti i +livelli) e conta il numero di occorrenze delle words nei file di testo +(quelli che hanno '.txt' come estensione) a tutti i livelli. Una +parola appare in un file se è separata dalla precedente o dalla +seguente da almeno uno spazio, tab, o newline. + +(3 points) +La funzione torna una lista di coppie (word, occorrenze) in cui il +primo elemento è la word ed il secondo è il numero di occorrenze +trovate. Se una word non appare in alcun file il suo numero di +occorrenze è 0. + +(+ 3 points) +Ordinate la lista di coppie in ordine decrescente di numero di +occorrenze ed in caso di parità in ordine alfabetico crescente. + +NOTA 1: potete usare le funzioni: os.listdir, os.path.join, +os.path.isfile, os.mkdir, os.path.exists ... +NOTA 2: è proibito usare la funzione os.walk +NOTA 3: usate il carattere '/' come separatore dei path +(che funzione sia in Windows che su MacOS o Linux) + +Esempio: +se il path dirin è "ex2" e le words = ["cat", "dog"] +la funzione ritorna: [('dog', 10), ('cat', 5)] +''' + +import os +def ex2(dirin, words): + pass + diz = {w:0 for w in words} + _ex2(dirin,diz) + def criterio(pair): + word,count = pair + return -count, word + return sorted(diz.items(), key=criterio) + +def _ex2(dirin, diz): + for name in os.listdir(dirin): + fullname = dirin + '/' + name + if os.path.isdir(fullname): + _ex2(fullname, diz) + else: + if name.endswith('.txt'): + with open(fullname, encoding='utf8') as FIN: + parole = FIN.read().split() + for w in diz: + diz[w] += parole.count(w) + + +###################################################################################### + +if __name__ == '__main__': + pass + from random import randint, choice + def creadiagonale(maxxy): + UD = choice(['diagonalUP', 'diagonalDOWN']) + R = randint(0, 255) + G = randint(0, 255) + B = randint(0, 255) + x = randint(-50, maxxy) + y = randint(-50, maxxy) + L = randint(100, maxxy) + return UD, R, G, B, x, y, L + ID = 4 + N = 55 + FN = f'func5/in_{ID}.txt' + with open(FN, mode='w') as F: + for _ in range(N): + print(*creadiagonale(500), sep=' ', file=F) + + diff --git a/Esami/2022-2023/Esame-7/program.aspo.py b/Esami/2022-2023/Esame-7/program.aspo.py new file mode 100644 index 0000000..06c06ce --- /dev/null +++ b/Esami/2022-2023/Esame-7/program.aspo.py @@ -0,0 +1,371 @@ + + + + +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +################################################################################ +################################################################################ +################################################################################ + +""" Operazioni da fare PRIMA DI TUTTO: + 1) Salvare il file come program.py + 2) Assegnare le variabili sottostanti con il tuo + NOME, COGNOME, NUMERO DI MATRICOLA + +Per superare l'esame è necessario: + - !!!riempire le informazioni personali nelle variabili qui sotto!!! + - AND risolvere almeno 1 esercizio di tipo ex (problema ricorsivo) + - AND risolvere almeno 3 esercizi di tipo func + - AND ottenere un punteggio maggiore o uguale a 18 + +Il punteggio finale è la somma dei punteggi dei problemi risolti. +""" +name = 'NAME' +surname = 'AA' +student_id = 'STUDENT_ID' # your Sapienza registration number + +################################################################################ +################################################################################ +################################################################################ +# ---------------------------- DEBUG SUGGESTIONS ----------------------------- # +# To run only some of the tests, you can comment the entries with which the +# 'tests' list is assigned at the end of grade.py +# +# To debug recursive functions you can turn off the recursive test setting +# DEBUG=True in the file grade.py +# +# DEBUG=True turns on also the STACK TRACE that allows you to know which +# line number in program.py generated the error. +################################################################################ + +# ---------------------------- FUNC 1 ---------------------------- # + +''' +Func 1: 2 punti +Si definisca la funzione func1(diz1, diz2) che riceve come argomenti +due dizionari che hanno chiavi intere e valori liste di stringhe. La +funzione deve tornare il dizionario che contiene le sole chiavi in +comune ad entrambi i dizionari. I valori associati a ciascuna chiave +sono quelli che appaiono in una sola delle due liste associate a +quella chiave nei due dizionari. Questi valori, senza ripetizioni, +vanno ordinati in ordine di lunghezza decrescente ed in caso di parità +in ordine alfabetico crescente. + +Esempio: +diz1 = { 1: ['a', 'bc', 'a'], 2: ['b', 'cr', 'e'], 3: ['a', 'qrt', 'st'] } +diz2 = { 1: ['a', 'cd', 'f'], 5: ['b', 'cr', 'e'], 3: ['a', 'bn', 'c'] } +il risultato sarà { 1: ['bc', 'cd', 'f'], 3: ['qrt', 'bn', 'st', 'c'] } +''' + +def func1(diz1, diz2): + diz3 = {} + for k in diz1.keys(): + if k in diz2: + diz3[k] = [v for v in diz1[k] if v not in diz2[k]] + [v for v in diz2[k] if v not in diz1[k]] + diz3[k].sort(key=lambda x: (-len(x), x)) + return diz3 + +#diz1 = { 1: ['a', 'bc', 'a'], 2: ['b', 'cr', 'e'], 3: ['a', 'qrt', 'st'] } +#diz2 = { 1: ['a', 'cd', 'f'], 5: ['b', 'cr', 'e'], 3: ['a', 'bn', 'c'] } +#print(func1(diz1,diz2)) + +#%% ---------------------------- FUNC 2 ---------------------------- # + +''' +Func 2: 2 punti + +Si definisca la funzione func2(text) che riceve come argomento: +- text: una stringa formata da parole separate da spazi +e che ritorna un dizionario che ha: + - come chiavi la lettera iniziale delle parole presenti, minuscola + - come valore il numero di parole che contengono quella lettera + ignorando la differenza tra minuscole e maiuscole + +Esempio: +text = 'sOtto lA panca La caPra Canta Sopra LA Panca La CaPra crepa' +expected = { 's':2, 'l':4, 'p':6, 'c':6} +''' + +def func2(text): + diz = {} + words = text.lower().split() + for w in words: + c = w[0] + if c not in diz: + diz[c] = sum(c in w for w in words) + return diz + +text = 'sOtto lA panca La caPra Canta Sopra LA Panca La CaPra crepa' +print(func2(text)) + +#%% ---------------------------- FUNC 3 ---------------------------- # +''' +Func 3: 4 punti +Definite la funzione func3(textfile_in, textfile_out) che riceve come argomento: +- textfile_in: il percorso di un file di testo da leggere +- textfile_out: il percorso di un file di testo da creare + +I file indicato da textfile_in contiene dei numeri +float oppure interi, positivi o negativi, separati da spazi. + +La funzione deve leggere i numeri, ordinarli in ordine decrescente di +numero di cifre significative, e in caso di parità in ordine crescente +di valore. Le cifre significative sono quelle che restano ignorando +punto e segno. + +Quindi deve scrivere questi numeri ordinati nel file textfile_out, +separati da virgola e spazio Infine la funzione restituisce la +quantita' dei numeri letti da textfile_in. + +Esempio: +se il file textfile_in contiene la riga +-23.5 17 -141 +322.7 -3227 +Nel file textfile_out la funzione deve scrivere la riga +-3227, +322.7, -141, -23.5, 17 +e tornare il valore 5 +''' + +def func3(textfile_in, textfile_out): + def sort_func(x): + n = x.strip('-+') + l = len(n) + if '.' in n: + # l = n.index('.') + l -=1 + return -l, float(x) + + with open(textfile_in) as f: + numbers = f.read().split() + numbers.sort(key = sort_func) + with open(textfile_out, 'w', encoding='utf8') as f: + print(', '.join(numbers), file = f) + return len(numbers) + +print(func3('func3/in_1.txt', 'func3/out_1.txt')) +#%% ---------------------------- FUNC 4 ---------------------------- # + +''' +Func 4: 4 punti +Si definisca la funzione func5(filein) che riceve come argomento +- filein: un file di testo contenente una matrice di interi NxM + separati da spazi + +e che ritorna la matrice trasposta rispetto alla diagonale secondaria, +ovvero quella che va dall'elemento in alto a destra a quello in basso +a sinistra. La matrice da restituire e' rappresentata come lista di liste. + +Esempio: +se il file filein contiene la matrice +1 2 3 4 +5 6 7 8 +9 10 11 12 +la funzione dovrà tornare la matrice riflessa rispetto alla diagonale 4-9, come lista di liste +[[12, 8, 4], + [11, 7, 3], + [10, 6, 2], + [ 9, 5, 1]] +''' +def func4(input_filename): + + with open(input_filename) as f: + lines = f.readlines() + M = [[] for _ in range(len(lines[0].split()))] + for line in lines: + for i, v in enumerate(line.split()[::-1]): + M[i].insert(0, int(v)) + return M + +print(func4('func4/in_1.txt')) +#%% ---------------------------- FUNC 5 ---------------------------- # + +''' +Func 5: 8 punti +Si definisca la funzione func5(txt_input, width, height, png_output) che riceve come argomenti + +- txt_input: il percorso di un file che contiene un elenco di figure da disegnare +- width: larghezza in pixel dell'immagine da creare +- height: altezza in pixel dell'immagine da creare +- png_output: il percorso di una immagine PNG che dovete creare, contenente le figure + +La funzione deve creare una immagine a sfondo nero e disegnarci sopra +tutte le figure indicate nel file 'txt_input', nell'ordine in cui +appaiono nel file. + +Il file txt_file contiene, una per riga, separate da spazi: +- una parola che indica il tipo di figura da disegnare +- le tre componenti R G B del colore da usare +- le coordinate e gli altri parametri necessari a definire la figura +Possono essre presenti 2 tipi di figura: +- diagonale discendente di un quadrato (in direzione -45°) + diagonalDOWN R G B x y L + La diagonale inizia nel punto (x,y), si dirige in BASSO a destra, ed è lunga L pixel +- diagonale ascendente di un quadrato (in direzione +45°) + diagonalUP R G B x y L + La diagonale inizia nel punto (x,y), si dirige in ALTO a destra, ed è lunga L pixel + +Quindi deve salvare l'immagine ottenuta nel file 'png_output' usando la funzione images.save. +Inoltre deve ritornare il numero di diagonali disegnate dei due tipi +come tupla dei due valori (DIAGUP,DIAGDOWN) + +NOTA: va gestito correttamente lo sbordare delle figure dalla immagine, +infatti sono ammesse anche coordinate negative, +e dimensioni o parametro L tali da far sbordare la figura dalla immagine + +Esempio: se il file func5/in_1.txt contiene le 3 righe +diagonalDOWN 0 255 0 -30 -40 110 +diagonalUP 255 0 0 20 100 200 +diagonalUP 0 0 255 10 120 50 + +l'esecuzione della funzione func5('func5/in_1.txt', 50, 100, 'func5/your_image_1.png') +produrrà una figura uguale al file 'func5/expected_1.png' +e tornerà la coppia (2, 1) +''' + +import images +def draw(im, diag, color, x, y, L, res): + i = 1 + if 'UP' in diag: + i=-1 + res[0]+=1 + else: + res[1]+=1 + for l in range(L): + if 0<=x+lP else 0 + tot = root.value + if len(root.sons) > 0: + for son in root.sons: + tot += ex1(son, valori, P+1) + return tot + + + +#%% ---------------------------- EX 2 ---------------------------- # + +''' +Ex2: 3 + 3 points +Definite la funzione ex2(dirin, words), ricorsiva o che usa funzioni o metodi ricorsivi, +che riceve come argomenti: + - dirin: il path di una directory + - words: una lista di parole + +La funzione esplora dirin e tutte le sue sottodirectory (a tutti i +livelli) e conta il numero di occorrenze delle words nei file di testo +(quelli che hanno '.txt' come estensione) a tutti i livelli. Una +parola appare in un file se è separata dalla precedente o dalla +seguente da almeno uno spazio, tab, o newline. + +(3 points) +La funzione torna una lista di coppie (word, occorrenze) in cui il +primo elemento è la word ed il secondo è il numero di occorrenze +trovate. Se una word non appare in alcun file il suo numero di +occorrenze è 0. + +(+ 3 points) +Ordinate la lista di coppie in ordine decrescente di numero di +occorrenze ed in caso di parità in ordine alfabetico crescente. + +NOTA 1: potete usare le funzioni: os.listdir, os.path.join, +os.path.isfile, os.mkdir, os.path.exists ... +NOTA 2: è proibito usare la funzione os.walk +NOTA 3: usate il carattere '/' come separatore dei path +(che funzione sia in Windows che su MacOS o Linux) + +Esempio: +se il path dirin è "ex2" e le words = ["cat", "dog"] +la funzione ritorna: [('dog', 11), ('cat', 6)] +''' + +import os + + +def ex2(dirin, words): + diz = ex2_(dirin, words) + return sorted(diz.items(), key = lambda t: (-t[1], t[0])) + +def ex2_(dirin, words): + def process(file, words): + with open(file) as f: + text = f.read().split() + return {w: text.count(w) for w in words} + + ret = {} + for f in os.listdir(dirin): + partial = {} + f = dirin + '/' + f + if os.path.isfile(f) and f.endswith('.txt'): + partial = process(f, words) + elif os.path.isdir(f): + partial = ex2_(f, words) + for w, c in partial.items(): + ret[w] = ret.get(w, 0) + c + return ret + +print(ex2('ex2/A', ['fish', 'cat', 'gnu'])) + + +#%%##################################################################################### + +if __name__ == '__main__': + pass + diff --git a/Esami/2022-2023/Esame-7/program.empty.py b/Esami/2022-2023/Esame-7/program.empty.py new file mode 100644 index 0000000..e895b53 --- /dev/null +++ b/Esami/2022-2023/Esame-7/program.empty.py @@ -0,0 +1,288 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +################################################################################ +################################################################################ +################################################################################ + +""" Steps to do FIRST: + 1) save this file as program.py + 2) assign the variables below with your + FIRST NAME, LAST NAME, STUDENT ID (Sapienza matriculation number) + +To pass the exam it is necessary to: + - !!!fill in your personal information in the variables below!!! + - AND solve at least 1 ex-type exercise (recursive problem) + - AND solve at least 3 func-type exercises + - AND obtain a score greater than or equal to 18 + +The final score is the sum of the scores of the solved problems. +""" + +name = 'NAME' +surname = 'SURNAME' +student_id = 'STUDENT_ID' # your Sapienza registration number + +################################################################################ +################################################################################ +################################################################################ +# ---------------------------- DEBUG SUGGESTIONS ----------------------------- # +# To run only some of the tests, you can comment the entries with which the +# 'tests' list is assigned at the end of grade.py +# +# To debug recursive functions you can turn off the recursive test setting +# DEBUG=True in the file grade.py +# +# DEBUG=True turns on also the STACK TRACE that allows you to know which +# line number in program.py generated the error. +################################################################################ + +#%% ---------------------------- FUNC 1 ---------------------------- # + +''' +Func 1: 2 points + +Define the function func1(dict1, dict2) that receives as arguments two +dictionaries that have integer keys and string list values. +The function must return the dictionary that contains the keys in common to +both dictionaries. +The values associated with each key are those that appear in only one of the +two lists associated with that key in the two dictionaries. +These values, without repetition, should be sorted in order of decreasing length +and in case of equality in ascending alphabetical order. + +Example: +dict1 = { 1: ['a', 'bc', 'a'], 2: ['b', 'cr', 'e'], 3: ['a', 'qrt', 'st'] } +dict2 = { 1: ['a', 'cd', 'f'], 5: ['b', 'cr', 'e'], 3: ['a', 'bn', 'c'] } +the result is { 1: ['bc', 'cd', 'f'], 3: ['qrt', 'bn', 'st', 'c'] } +''' + +def func1(dict1, dict2): + pass + +#diz1 = { 1: ['a', 'bc', 'a'], 2: ['b', 'cr', 'e'], 3: ['a', 'qrt', 'st'] } +#diz2 = { 1: ['a', 'cd', 'f'], 5: ['b', 'cr', 'e'], 3: ['a', 'bn', 'c'] } +#print(func1(diz1,diz2)) + +#%% ---------------------------- FUNC 2 ---------------------------- # + +''' +Func 2: 2 points + +Define the function func2(text) which receives as an argument: + - text: a string consisting of words separated by spaces +and which returns a dictionary that has: + - as keys the initial letters of the words, lower case + - as values the number of words that contain that letter, + ignoring the difference between lower and upper case + +Example: +text = 'sOtto lA panca La caPra Canta Sopra LA Panca La CaPra crepa' +expected = { 's':2, 'l':4, 'p':6, 'c':6} +''' + +def func2(text): + pass + +# text = 'sOtto lA panca La caPra Canta Sopra LA Panca La CaPra crepa' +# print(func2(text)) + +# ---------------------------- FUNC 3 ---------------------------- # +''' +Func 3: 4 points +Define the function func3(textfile_in, textfile_out) which receives as arguments: +- textfile_in: the path to a text file to read +- textfile_out: the path to a text file to create + +The file at the path textfile_in contains either strings representing +floats or integers, positive or negative, separated by spaces. + +The function must read the strings, sort them in descending order +based on the number of numerical digits (ignoring dot and sign), and +in case of equality in ascending value of the represented number. + +Then it must write these sorted numbers into the textfile_out file, +separated by comma and space. +Finally, the function returns the number of numbers read from textfile_in. + +Example: +if the file textfile_in contains the line +-23.5 17 -141 +322.7 -3227 +In the textfile_out file, the function should write the line +-3227, +322.7, -141, -23.5, 17 +and return the value 5 +''' + +def func3(textfile_in, textfile_out): + pass + +# print(func3('func3/in_1.txt', 'func3/out_1.txt')) + +#%% ---------------------------- FUNC 4 ---------------------------- # + +''' +Func 4: 4 points +Define the function func4(filein) that receives as argument +- filein: a text file containing a matrix of NxM integers separated by spaces + +and which returns the matrix transposed with respect to its secondary diagonal +(i.e., the one going from the top right element to the bottom left element) +represented as a list of lists. + +Example: +if filein contains the matrix: +1 2 3 4 +5 6 7 8 +9 10 11 12 + +the function should return the matrix reflected with respect to the diagonal +4-9, as a list of lists: +[[12, 8, 4], + [11, 7, 3], + [10, 6, 2], + [ 9, 5, 1]] +''' +def func4(input_filename): + pass + +#print(func4('func4/in_1.txt')) + +#%% ---------------------------- FUNC 5 ---------------------------- # + +''' +Func 5: 8 points + +Define the function func5(txt_input, width, height, png_output) that +receives as arguments: +- txt_input: the path to a file containing a list of figures to be drawn +- width: width in pixels of the image to be created +- height: height in pixels of the image to be created +- png_output: the path to a PNG image you need to create, containing the figures + +The function should create a black background image and draw all the figures +indicated in the 'txt_input' file, in the order they appear in the file. + +The txt_file contains, one per line, separated by spaces: +- a word indicating the type of figure to be drawn +- the three R G B components of the color to be used +- the coordinates and other parameters needed to define the figure. +There can be 2 types of figure: +- descending diagonal of a square (-45° direction): + diagonalDOWN R G B x y L + The diagonal begins at the point (x,y), heads LOW-right, and is L pixels long +- ascending diagonal of a square (+45° direction): + diagonalUP R G B x y L + The diagonal starts at the point (x,y), heads UP-right, and is L pixels long + +Then it must save the obtained image in the file 'png_output' using the +images.save function. +It must also return the number of diagonals drawn of the two types +as a tuple of the two values (DIAGUP, DIAGDOWN). + +NOTE: the points of the figures outside the image must be handled correctly, +in fact, negative coordinates are also allowed, +and dimensions or parameter L such that parts of the figure are outside the image. + +Example: if the file func5/in_1.txt contains the 3 figures: +diagonalDOWN 0 255 0 -30 -40 110 +diagonalUP 255 0 0 20 100 200 +diagonalUP 0 0 255 10 120 50 + +running the function func5('func5/in_1.txt', 50, 100, 'func5/your_image_1.png') +will produce the figure in the file 'func5/expected_1.png' +and will return the pair (2, 1) +''' + +import images +def func5(txt_input, width, height, png_output): + pass + +#func5('func5/in_1.txt', 50, 100, 'func5/your_image_1.png') +# ---------------------------- EX 1 ---------------------------- # + +''' +Ex1: recursive, 6 points + +Let us define the function ex1(root, values), recursive or using recursive +functions, which receives as input +- the root 'root' of an n-ary tree defined by nodes nary_tree.NaryTree +- a list of integers 'values' +which destructively modifies the 'root' tree by adding all nodes that are at depth P +(assuming the root is at depth 0) to the value that in the 'values' list is +at index P (if it exists, otherwise they remain as they are). + +The function must return the sum 'total' of all the nodes in the resulting tree. + +IMPORTANT: the recursive function must be define at the outmost level, +that is, with the keyword 'def' located at the beginning of the line. + +Example: + values: [-42, -80, 68, 2, 81, 75, 54, 48, -4, 5] to be added up: + root: -7 | -42 + / | | | \ | + -10 -3 -8 -10 -5 | -80 + / \ | | | | + 6 -2 9 7 -9 | +68 + + expected: -49 | + / | | | \ | + -90 -83 -88 -90 -85 | + / \ | | | | + 74 66 77 75 59 | + total = -134 + +''' + +from nary_tree import NaryTree + +def ex1(root : NaryTree, valori : list[int]): + pass + + +# %% ----------------------------------- EX.2 ----------------------------------- # +''' +Ex2: recursive, 3 + 3 points + Define the function ex2(dirin, words), recursive or using recursive + functions, having as arguments: + - dirin: the path to a directory + - words: a list of words + + The function will examine dirin and all its subdirectories (at any level), + and will count the occurrences of words in the words list in all text files + (i.e., those having the extension .txt) in any folder. + A word is present in a file if and only if it is separated from the preceding word + or the next word, if any, by a space, tab, or newline character. + + (3 points) + The function returns a list of tuples (word, occurrences), + where the first value of each tuple is one of the words in the words list + and the second is the number of occurrences of that word in all the text files that + have been found. + + (+ 3 points) + The list is sorted by the number of occurrences of the words + (in descending order); if two or more words have the same number of occurrences, + they are sorted alphabetically (in ascending order). + If a word in the words list never occurs, it must still be returned by the function. + + NOTICE 1: useful functions could be os.listdir, + os.path.isfile, os.mkdir, os.path.exists ... + NOTICE 2: it is forbidden to use the os.walk function. + NOTICE 3: use the '/' char as path separator (it works well in Windows, MacOS or Linux) + + For example, given folder = "ex2" and words = ["cat", "dog"] + the function returns: [("dog", 10), ("cat", 5)] + +''' + +import os +def ex2(dirin, words): + pass + +###################################################################################### + +if __name__ == '__main__': + print('*' * 50) + print('ENG\nYou have to run grade.py if you want to debug with the automatic grader.') + print('Otherwise you can insert here you code to test the functions but you have to write your own tests') + print('*' * 50) diff --git a/Esami/2022-2023/Esame-7/program.iac.py b/Esami/2022-2023/Esame-7/program.iac.py new file mode 100644 index 0000000..9a35064 --- /dev/null +++ b/Esami/2022-2023/Esame-7/program.iac.py @@ -0,0 +1,327 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +################################################################################ +################################################################################ +################################################################################ + +""" Operazioni da fare PRIMA DI TUTTO: + 1) Salvare il file come program.py + 2) Assegnare le variabili sottostanti con il tuo + NOME, COGNOME, NUMERO DI MATRICOLA + +Per superare l'esame è necessario: + - riempire le informazioni personali nelle variabili poco puù giù + - risolvere almeno 3 esercizi di tipo func AND; + - risolvere almeno 1 esercizio di tipo ex (problema ricorsivo) AND; + - ottenere un punteggio maggiore o uguale a 18 + +Il voto finale è la somma dei punteggi dei problemi risolti. +""" +nome = "Lacopo" +cognome = "Massi" +matricola = "12345" + +#name = 'NAME' +#surname = 'SURNAME' +#student_id = 'STUDENT_ID' # your Sapienza registration number + +################################################################################ +################################################################################ +################################################################################ +# ---------------------------- DEBUG SUGGESTIONS ----------------------------- # +# To run only some of the tests, you can comment the entries with which the +# 'tests' list is assigned at the end of grade.py +# +# To debug recursive functions you can turn off the recursive test setting +# DEBUG=True in the file grade.py +# +# DEBUG=True turns on also the STACK TRACE that allows you to know which +# line number in program.py generated the error. +################################################################################ + +# ---------------------------- FUNC 1 ---------------------------- # + +''' +Func 1: 2 punti +Si definisca la funzione func1(diz1, diz2) che riceve come argomenti due dizionari che hanno +chiavi intere e valori liste di stringhe. +La funzione deve tornare il dizionario che contiene le sole chiavi in comune ad entrambi i dizionari. +I valori associati a ciascuna chiave sono quelli che appaiono in una sola delle due liste +senza ripetizioni e in ordine di lunghezza decrescente ed in caso di parità in ordine alfabetico. + +Esempio: +diz1 = { 1: ['a', 'bc', 'a'], 2: ['b', 'cr', 'e'], 3: ['a', 'qrt', 'st'] } +diz2 = { 1: ['a', 'cd', 'f'], 5: ['b', 'cr', 'e'], 3: ['a', 'bn', 'c'] } +il risultato sarà { 1: ['bc', 'cd', 'f'], 3: ['qrt', 'bn', 'st', 'c'] } +''' + + +def func1(diz1, diz2): + return {k : sorted(set(diz1[k])^set(diz2[k]),key=lambda S: (-len(S), S)) for k in diz1.keys() & diz2.keys() } + +#diz1 = { 1: ['a', 'bc', 'a'], 2: ['b', 'cr', 'e'], 3: ['a', 'qrt', 'st'] } +#diz2 = { 1: ['a', 'cd', 'f'], 5: ['b', 'cr', 'e'], 3: ['a', 'bn', 'c'] } +#print(func1(diz1,diz2)) + +# ---------------------------- FUNC 2 ---------------------------- # + + +''' +Func 2: 2 punti + +Si definisca la funzione func2(stringlist) che riceve come argomento: +- text: una stringa formata da parole separate da spazi +e che ritorna un dizionario che ha: + - come chiavi la lettera iniziale delle parole presenti, minuscola + - come valore il numero di parole che contengono quella lettera ignorando la differenza tra minuscole e maiuscole + +Esempio: +text = 'sOtto lA panca La caPra Canta Sopra LA Panca La CaPra crepa' +expected = { 's':2, 'l':4, 'p':6, 'c':6} +''' + + +def func2(text): + D = {w[0].lower(): 0 for w in text.split()} + for w in text.split(): + for k in D: + if k in w.lower(): + D[k] += 1 + return D + + +# ---------------------------- FUNC 3 ---------------------------- # +''' +Func 3: 4 punti +Definite la funzione func3(textfile_in, textfile_out) che riceve come argomento: +- textfile_in: il percorso di un file di testo da leggere +- textfile_out: il percorso di un file di testo da creare + +I file indicato da textfile_in contiene dei numeri +float oppure interi, positivi o negativi, separati da spazi. + +La funzione deve leggere i numeri, ordinarli in ordine decrescente di numero di cifre significative, +e in caso di parità in ordine crescente di valore. + +Quindi deve scrivere questi numeri ordinati nel file textfile_out, separati da virgola e spazio + +Esempio: +se il file textfile_in contiene la riga +-23.5 17 -141 +322.7 -3227 +Nel file textfile_out la funzione deve scrivere la riga +-3227, +322.7, -141, -23.5, 17 +''' +def func3(textfile_in, textfile_out): + with open(textfile_in) as fr, open(textfile_out, mode='w') as fw: + num = next(iter(fr)).split() + print(*sorted(num, key=lambda S: (-len(S.strip('-').strip('+').replace('.','')), float(S))), + file=fw, sep=', ') + return len(num) + +# ---------------------------- FUNC 4 ---------------------------- # + + +''' +Func 4: 4 punti +Si definisca la funzione func5(filein) che riceve come argomento +- filein: un file di testo contenente una matrice di interi NxM separati da spazi + +e che ritorna la matrice trasposta rispetto alla diagonale secondaria +(ovvero quella che va dall'elemento in alto a destra a quello in basso a sinistra) +rappresentata come lista di liste. + +Esempio: +se il file filein contiene la matrice +1 2 3 4 +5 6 7 8 +9 10 11 12 +la funzione dovrà tornare la matrice riflessa rispetto alla diagonale 4-9 +[[12, 8, 4], + [11, 7, 3], + [10, 6, 2], + [ 9, 5, 1]] +''' + +def func4(input_filename): + with open(input_filename) as fr: + mat = [ [int(p) for p in r.split(' ')] for r in fr] + H,W = len(mat), len(mat[0]) + return [[mat[r][c] for r in reversed(range(H))] for c in reversed(range(W))] + +# ---------------------------- FUNC 5 ---------------------------- # + + +''' +Func 5: 8 punti +Si definisca la funzione func5(txt_input, width, height, png_output) che riceve come argomenti +- txt_input: il percorso di un file che contiene un elenco di figure da disegnare +- width: larghezza in pixel dell'immagine da creare +- height: altezza in pixel dell'immagine da creare +- png_output: il percorso di una immagine PNG che dovete creare, contenente le figure + +La funzione deve creare una immagine a sfondo nero e disegnarci sopra tutte le figure +indicate nel file 'txt_input', nell'ordine in cui appaiono nel file. + +Il file txt_file contiene, una per riga, separate da spazi: +- una parola che indica il tipo di figura da disegnare +- le tre componenti R G B del colore da usare +- le coordinate e gli altri parametri necessari a definire la figura +Possono essre presenti 2 tipi di figura: +- diagonale discendente di un quadrato (in direzione -45°) + diagonalDOWN R G B x y L + La diagonale inizia nel punto (x,y), si dirige in BASSO a destra, ed è lunga L pixel +- diagonale ascendente di un quadrato (in direzione +45°) + diagonalUP R G B x y L + La diagonale inizia nel punto (x,y), si dirige in ALTO a destra, ed è lunga L pixel + +Quindi deve salvare l'immagine ottenuta nel file 'png_output' usando la funzione images.save. +Inoltre deve ritornare il numero di diagonali disegnate dei due tipi +come tupla dei due valori (DIAGUP,DIAGDOWN) + +NOTA: va gestito correttamente lo sbordare delle figure dalla immagine, +infatti sono ammesse anche coordinate negative, +e dimensioni o parametro L tali da far sbordare la figura dalla immagine + +Esempio: se il file func5/in_1.txt contiene le 3 righe +diagonalDOWN 0 255 0 -30 -40 110 +diagonalUP 255 0 0 20 100 200 +diagonalUP 0 0 255 10 120 50 + +l'esecuzione della funzione func5('func5/in_1.txt', 50, 100, 'func5/your_image_1.png') +produrrà una figura uguale al file 'func5/expected_1.png' +e tornerà la coppia (2, 1) +''' + +import images + +def diag(im, r, g, b, x, y, L, width, height, tipo): + for l in range(L): + dy, dx = (y+l, x+l) if tipo == 'diagonalDOWN' else (y-l, x+l) + if all([dy >=0, dy < height, dx >=0, dx < width]): + im[dy][dx] = (r, g, b) + +def func5(txt_input, width, height, png_output): + black = 0, 0, 0 + im = [[black for _ in range(width)] for _ in range(height)] + diz = dict(diagonalUP=0, diagonalDOWN=0) + with open(txt_input) as fr: + for line in fr: + tipo, *data = line.split() + data = [int(d) for d in data] + diz[tipo] += 1 + diag(im, *data, width, height, tipo) + images.save(im, png_output) + return tuple(diz.values()) + +# print(func5('func5/in_1.txt', 50, 100, 'func5/out_1.png')) + + +# ---------------------------- EX 1 ---------------------------- # + +''' +Esercizio 1 ricorsivo (6 punti): + +Si definisca la funzione es1(root, valori), ricorsiva o che usa funzioni ricorsive, +che riceve in input +- la radice 'root' di un albero n-ario definito da nodi nary_tree.NaryTree +- una lista di interi 'valori' +che modifica distruttivamente l'albero 'root' sommando a tutti i nodi che sono a profondità P +(assumendo che la radice si trovi a profondità 0) il valore che nella lista 'valori' +si trova all'indice P (se esiste, altrimenti restano come sono). + +La funzione deve restituire la somma 'total' di tutti i nodi dell'albero risultante. + +Esempio: + values: [-42, -80, 68, 2, 81, 75, 54, 48, -4, 5] da sommare + root: -7 | -42 + / | | | \ | + -10 -3 -8 -10 -5 | -80 + / \ | | | | + 6 -2 9 7 -9 | +68 + + expected: -49 | + / | | | \ | + -90 -83 -88 -90 -85 | + / \ | | | | + 74 66 77 75 59 | + total = -134 + +ATTENZIONE: definite la funzione ricorsiva a livello esterno, +ovvero con la parola chiave 'def' appoggiata all'inizio della riga. +''' + +from nary_tree import NaryTree + + +def explore(node, values, depth=0): + V, summ = 0, 0 + try: + V = values[depth] + except IndexError as ie: + pass + node.value += V + return sum(explore(son, values, depth=depth+1) for son in node.sons) + node.value + + +def ex1(root : NaryTree, valori : list[int]): + return explore(root, valori) + + +# %% ----------------------------------- EX.2 ----------------------------------- # +''' +Ex2: 3 + 3 points +Definite la funzione ex2(dirin, words), ricorsiva o che usa funzioni o metodi ricorsivi, +che riceve come argomenti: +- dirin: il path di una directory +- words: una lista di parole +La funzione esplora dirin e tutte le sue sottodirectory (a tutti i livelli) +e conta il numero di occorrenze delle words nei file di testo +(quelli che hanno '.txt' come estensione) a tutti i livelli. +Una parola appare in un file se è separata dalla precedente o dalla seguente da almeno +uno spazio, tab, o newline. + +(3 points) +La funzione torna una lista di coppie (word, occorrenze) in cui +il primo elemento è la word ed il secondo è il numero di occorrenze trovate. +Se una word non appare in alcun file il suo numero di occorrenze è 0. +(+ 3 points) +Ordinate la lista di coppie in ordine decrescente di numero di occorrenze +ed in caso di parità in ordine alfabetico crescente. + +NOTA 1: potete usare le funzioni: os.listdir, os.path.join, +os.path.isfile, os.mkdir, os.path.exists ... +NOTA 2: è proibito usare la funzione os.walk +NOTA 3: usate il carattere '/' come separatore dei path +(che funzione sia in Windows che su MacOS o Linux) + +Esempio: +se il path dirin è "ex2" e le words = ["cat", "dog"] +la funzione ritorna: [('dog', 11), ('cat', 6)] +''' +import os + + +def parse_file(full_path, out): + with open(full_path) as fr: + for w in fr.read().split(): + if w in out: + out[w] += 1 + +def ex2(dirin, words, out=None, start=0): + if out is None: + out, start = {w: 0 for w in words}, 1 + for item in os.listdir(dirin): + full_path = dirin + '/' + item + if os.path.isfile(full_path) and item.endswith('.txt'): + parse_file(full_path, out) + elif os.path.isdir(full_path): + ex2(full_path, words, out) + return sorted(((k, v) for k, v in out.items()), key=lambda T: (-T[1], T[0])) if start else None + + +if __name__ == '__main__': + pass + + + diff --git a/Esami/2022-2023/Esame-7/program.mauman.py b/Esami/2022-2023/Esame-7/program.mauman.py new file mode 100644 index 0000000..93767bd --- /dev/null +++ b/Esami/2022-2023/Esame-7/program.mauman.py @@ -0,0 +1,393 @@ + + + + +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +################################################################################ +################################################################################ +################################################################################ + +""" Steps to do FIRST: + 1) save this file as program.py + 2) assign the variables below with your + FIRST NAME, LAST NAME, STUDENT ID (Sapienza matriculation number) + +To pass the exam it is necessary to: + - !!!fill in your personal information in the variables below!!! + - AND solve at least 1 ex-type exercise (recursive problem) + - AND solve at least 3 func-type exercises + - AND obtain a score greater than or equal to 18 + +The final score is the sum of the scores of the solved problems. +""" +nome = "nome" +cognome = "cognome" +matricola = "matricola" + +name = 'name' +surname = 'surname' +student_id = 'student_id' # your Sapienza registration number + +################################################################################ +################################################################################ +################################################################################ +# ---------------------------- DEBUG SUGGESTIONS ----------------------------- # +# To run only some of the tests, you can comment the entries with which the +# 'tests' list is assigned at the end of grade.py +# +# To debug recursive functions you can turn off the recursive test setting +# DEBUG=True in the file grade.py +# +# DEBUG=True turns on also the STACK TRACE that allows you to know which +# line number in program.py generated the error. +################################################################################ + +# ---------------------------- FUNC 1 ---------------------------- # + +''' +Func 1: 2 points + +Define the function func1(dict1, dict2) that receives as arguments two dictionaries that have +integer keys and string list values. +The function must return the dictionary that contains the keys in common to both dictionaries. +The values associated with each key are those that appear in only one of the two lists +associated with that key in the two dictionaries. +These values, without repetition, should be sorted in order of decreasing length +and in case of equality in ascending alphabetical order. + +Example: +dict1 = { 1: ['a', 'bc', 'a'], 2: ['b', 'cr', 'e'], 3: ['a', 'qrt', 'st'] } +dict2 = { 1: ['a', 'cd', 'f'], 5: ['b', 'cr', 'e'], 3: ['a', 'bn', 'c'] } +the result is { 1: ['bc', 'cd', 'f'], 3: ['qrt', 'bn', 'st', 'c'] } +''' + +def func1(dict, dict): + pass + def criterio(s): return -len(s), s + return { + k : sorted(set(v1).symmetric_difference(set(diz2[k])), key=criterio) + for k,v1 in diz1.items() + if k in diz2 + } + +#diz1 = { 1: ['a', 'bc', 'a'], 2: ['b', 'cr', 'e'], 3: ['a', 'qrt', 'st'] } +#diz2 = { 1: ['a', 'cd', 'f'], 5: ['b', 'cr', 'e'], 3: ['a', 'bn', 'c'] } +#print(func1(diz1,diz2)) + +# ---------------------------- FUNC 2 ---------------------------- # + +''' +Func 2: 2 punti + +Define the function func2(text) which receives as an argument: + - text: a string consisting of words separated by spaces +and which returns a dictionary that has: + - as keys the initial letters of the words, lower case + - as values the number of words that contain that letter, + ignoring the difference between lower and upper case + +Example: +text = 'sOtto lA panca La caPra Canta Sopra LA Panca La CaPra crepa' +expected = { 's':2, 'l':4, 'p':6, 'c':6} +''' + +def func2(text): + pass + lower = text.lower().split() + diz = { parola[0]: 0 for parola in lower } + for parola in lower: + for carattere in diz: + if carattere in parola: + diz[carattere] += 1 + return diz + +# ---------------------------- FUNC 3 ---------------------------- # +''' +Func 3: 4 punti +Define the function func3(textfile_in, textfile_out) which receives as argument: +- textfile_in: the path to a text file to read +- textfile_out: the path to a text file to create + +The file at the path textfile_in contains either +floats or integers, positive or negative, separated by spaces. + +The function must read the numbers, sort them in descending order, +based on the number of significant digits, +and in case of equality in ascending value. +(significant digits are those remaining ignoring dot and sign.) + +Then it must write these sorted numbers into the textfile_out file, +separated by comma and space. +Finally, the function returns the number of numbers read from textfile_in. + +Example: +if the file textfile_in contains the line +-23.5 17 -141 +322.7 -3227 +In the textfile_out file, the function should write the line +-3227, +322.7, -141, -23.5, 17 +and return the value 5 +''' + +def func3(textfile_in, textfile_out): + def criterio(elemento): + cifre = elemento.replace('.','') + cifre = cifre.replace('-', '') + cifre = cifre.replace('+', '') + return -len(cifre), float(elemento) + with open(textfile_in) as FIN: + numeri = FIN.read().split() + numeri.sort(key = criterio) + with open(textfile_out, mode='w') as FOUT: + print(*numeri, sep=', ', file=FOUT) + return len(numeri) + + +# ---------------------------- FUNC 4 ---------------------------- # + +''' +Func 4: 4 points +Define the function func5(filein) that receives as argument +- filein: a text file containing a matrix of NxM integers separated by spaces + +and which returns the matrix transposed with respect to its secondary diagonal +(i.e., the one going from the top right element to the bottom left element) +represented as a list of lists. + +Example: +if filein contains the matrix: +1 2 3 4 +5 6 7 8 +9 10 11 12 +the function should return the matrix reflected with respect to the diagonal 4-9, as a list of lists: +[[12, 8, 4], + [11, 7, 3], + [10, 6, 2], + [ 9, 5, 1]] +''' +def func4(input_filename): + pass + with open(input_filename) as FIN: + M = [ list(map(int, riga.split())) for riga in FIN ] + W = len(M[0]) + H = len(M) + return [ [ M[H-1-y][W-1-x] + for y in range(H)] + for x in range(W)] + +# ---------------------------- FUNC 5 ---------------------------- # + +''' +Func 5: 8 points + +Define the function func5(txt_input, width, height, png_output) that receives as arguments: +- txt_input: the path to a file containing a list of figures to be drawn +- width: width in pixels of the image to be created +- height: height in pixels of the image to be created +- png_output: the path to a PNG image you need to create, containing the figures + +The function should create a black background image and draw all the figures +indicated in the 'txt_input' file, in the order they appear in the file. + +The txt_file contains, one per line, separated by spaces: +- a word indicating the type of figure to be drawn +- the three R G B components of the color to be used +- the coordinates and other parameters needed to define the figure +There can be 2 types of figure: +- descending diagonal of a square (-45° direction): + diagonalDOWN R G B x y L + The diagonal begins at the point (x,y), heads LOW-right, and is L pixels long +- Ascending diagonal of a square (+45° direction): + diagonalUP R G B x y L + The diagonal starts at the point (x,y), heads UP-right, and is L pixels long + +Then it must save the obtained image in the file 'png_output' using the images.save function. +It must also return the number of diagonals drawn of the two types +as a tuple of the two values (DIAGUP, DIAGDOWN). + +NOTE: the points of the figures outside the image must be handled correctly, +in fact, negative coordinates are also allowed, +and dimensions or parameter L such that parts of the figure are outside the image. + +Example: if the file func5/in_1.txt contains the 3 figures: +diagonalDOWN 0 255 0 -30 -40 110 +diagonalUP 255 0 0 20 100 200 +diagonalUP 0 0 255 10 120 50 + +running the function func5('func5/in_1.txt', 50, 100, 'func5/your_image_1.png') +will produce the figure in the file 'func5/expected_1.png' +and will return the pair (2, 1) +''' + +from math import dist +import images +def func5(txt_input, width, height, png_output): + pass + def draw_diagonalDown(img, W, H, x, y, L, color): + for i in range(L): + X,Y = x+i, y+i + if 0 <= X < W and 0 <= Y < H: + img[Y][X] = color + def draw_diagonalUp(img, W, H, x, y, L, color): + for i in range(L): + X,Y = x+i, y-i + if 0 <= X < W and 0 <= Y < H: + img[Y][X] = color + img = [ [(0,0,0)]*width for _ in range(height) ] # immagine vuota, nera + diagUP = diagDOWN = 0 + with open(txt_input) as F: + for line in F: + tipo, *data= line.split() + R, G, B, x, y, L = list(map(int, data)) + if tipo == 'diagonalDOWN': + draw_diagonalDown(img, width, height, x, y, L, (R, G, B)) + diagDOWN += 1 + elif tipo == 'diagonalUP': + draw_diagonalUp( img, width, height, x, y, L, (R, G, B)) + diagUP += 1 + images.save(img, png_output) + return diagUP, diagDOWN + +# print(func5('func5/in_1.txt', 50, 100, 'func5/out_1.png')) + + +# ---------------------------- EX 1 ---------------------------- # + +''' +Ex1: recursive, 6 points + +Let us define the function ex1(root, values), recursive or using recursive functions, +which receives as input +- the root 'root' of an n-ary tree defined by nodes nary_tree.NaryTree +- a list of integers 'values' +which destructively modifies the 'root' tree by adding all nodes that are at depth P +(assuming the root is at depth 0) to the value that in the 'values' list is +at index P (if it exists, otherwise they remain as they are). + +The function must return the sum 'total' of all the nodes in the resulting tree. + +IMPORTANT: the recursive function must be define at the outmost level, +that is, with the keyword 'def' located at the beginning of the line. + +Example: + values: [-42, -80, 68, 2, 81, 75, 54, 48, -4, 5] to be added up: + root: -7 | -42 + / | | | \ | + -10 -3 -8 -10 -5 | -80 + / \ | | | | + 6 -2 9 7 -9 | +68 + + expected: -49 | + / | | | \ | + -90 -83 -88 -90 -85 | + / \ | | | | + 74 66 77 75 59 | + total = -134 + + +''' + +from nary_tree import NaryTree + +def ex1(root : NaryTree, valori : list[int]): + return _ex1(root, valori, 0) + +def _ex1(root : NaryTree, valori : list[int], depth : int): + if depth < len(valori): + root.value += valori[depth] + total = root.value + for son in root.sons: + total += _ex1(son, valori, depth + 1) + return total + + +# %% ----------------------------------- EX.2 ----------------------------------- # +''' +Ex2: recursive, 4 + 2 points + Define the function ex2(dirin, words), recursive or using functions + or recursive functions, having as arguments: + - dirin: the path to a directory + - words: a list of words + The function will examine dirin and all its subdirectories (at any level), + and will count the occurrences of words in the words list in all text files + (i.e., those having the extension .txt) in any folder. + A word is present in a file if and only if it is separated from the preceding word + or the next word, if any, by a space, tab, or newline character. + + (5 points) The function returns a list of tuples (word, occurrences), + where the first value of each tuple is one of the words in the words list + and the second is the number of occurrences of that word in all the text files that + have been found. + (+ 3 points) The list is sorted by the number of occurrences of the words + (in descending order); if two or more words have the same number of occurrences, + they are sorted alphabetically (in ascending order). + If a word in the words list never occurs, it must still be returned by the function. + + NOTICE 1: useful functions could be os.listdir, + os.path.isfile, os.mkdir, os.path.exists ... + NOTICE 2: it is forbidden to use the os.walk function. + + For example, given folder = "ex2" and words = ["cat", "dog"] + the function returns: [("dog", 11), ("cat", 6)] + +''' + +import os + + +def RecScan(dir, words): + D = {} + items = os.listdir(dir) + for item in items: + if os.path.isfile(dir + "/" + item): + if item[-4:].lower() == ".txt": + fileRef = open(dir + "/" + item, "r", encoding="utf-8") + for line in fileRef: + tokens = line.strip().replace("\t", " ").split(" ") + for token in tokens: + if token in words: + if token not in D: + D[token] = 1 + else: + D[token] += 1 + fileRef.close() + else: + resD = RecScan(dir + "/" + item, words) + for k in resD: + if k not in D: + D[k] = resD[k] + else: + D[k] += resD[k] + return D + + +def ex2(dirin, words): + D = RecScan(dirin, words) + result = [] + for k in D: + result.append((k, D[k])) + return sorted(result, key=lambda x: x[1], reverse=True) + +###################################################################################### + +if __name__ == '__main__': + pass + from random import randint, choice + def creadiagonale(maxxy): + UD = choice(['diagonalUP', 'diagonalDOWN']) + R = randint(0, 255) + G = randint(0, 255) + B = randint(0, 255) + x = randint(-50, maxxy) + y = randint(-50, maxxy) + L = randint(100, maxxy) + return UD, R, G, B, x, y, L + ID = 4 + N = 55 + FN = f'func5/in_{ID}.txt' + with open(FN, mode='w') as F: + for _ in range(N): + print(*creadiagonale(500), sep=' ', file=F) + + diff --git a/Esami/2022-2023/Esame-7/program.maumanITA.py b/Esami/2022-2023/Esame-7/program.maumanITA.py new file mode 100644 index 0000000..2028674 --- /dev/null +++ b/Esami/2022-2023/Esame-7/program.maumanITA.py @@ -0,0 +1,389 @@ + + + + +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +################################################################################ +################################################################################ +################################################################################ + +""" Operazioni da fare PRIMA DI TUTTO: + 1) Salvare il file come program.py + 2) Assegnare le variabili sottostanti con il tuo + NOME, COGNOME, NUMERO DI MATRICOLA + +Per superare l'esame è necessario: + - !!!riempire le informazioni personali nelle variabili qui sotto!!! + - AND risolvere almeno 1 esercizio di tipo ex (problema ricorsivo) + - AND risolvere almeno 3 esercizi di tipo func + - AND ottenere un punteggio maggiore o uguale a 18 + +Il punteggio finale è la somma dei punteggi dei problemi risolti. +""" +nome = "nome" +cognome = "cognome" +matricola = "matricola" + +name = 'name' +surname = 'surname' +student_id = 'student_id' # your Sapienza registration number + +################################################################################ +################################################################################ +################################################################################ +# ---------------------------- DEBUG SUGGESTIONS ----------------------------- # +# To run only some of the tests, you can comment the entries with which the +# 'tests' list is assigned at the end of grade.py +# +# To debug recursive functions you can turn off the recursive test setting +# DEBUG=True in the file grade.py +# +# DEBUG=True turns on also the STACK TRACE that allows you to know which +# line number in program.py generated the error. +################################################################################ + +# ---------------------------- FUNC 1 ---------------------------- # + +''' +Func 1: 2 punti +Si definisca la funzione func1(diz1, diz2) che riceve come argomenti due dizionari che hanno +chiavi intere e valori liste di stringhe. +La funzione deve tornare il dizionario che contiene le sole chiavi in comune ad entrambi i dizionari. +I valori associati a ciascuna chiave sono quelli che appaiono in una sola delle due liste +associate a quella chiave nei due dizionari. +Questi valori, senza ripetizioni, vanno ordinati in ordine di lunghezza decrescente +ed in caso di parità in ordine alfabetico crescente. + +Esempio: +diz1 = { 1: ['a', 'bc', 'a'], 2: ['b', 'cr', 'e'], 3: ['a', 'qrt', 'st'] } +diz2 = { 1: ['a', 'cd', 'f'], 5: ['b', 'cr', 'e'], 3: ['a', 'bn', 'c'] } +il risultato sarà { 1: ['bc', 'cd', 'f'], 3: ['qrt', 'bn', 'st', 'c'] } +''' + +def func1(diz1, diz2): + pass + def criterio(s): return -len(s), s + return { + k : sorted(set(v1).symmetric_difference(set(diz2[k])), key=criterio) + for k,v1 in diz1.items() + if k in diz2 + } + +#diz1 = { 1: ['a', 'bc', 'a'], 2: ['b', 'cr', 'e'], 3: ['a', 'qrt', 'st'] } +#diz2 = { 1: ['a', 'cd', 'f'], 5: ['b', 'cr', 'e'], 3: ['a', 'bn', 'c'] } +#print(func1(diz1,diz2)) + +# ---------------------------- FUNC 2 ---------------------------- # + +''' +Func 2: 2 punti + +Si definisca la funzione func2(text) che riceve come argomento: +- text: una stringa formata da parole separate da spazi +e che ritorna un dizionario che ha: + - come chiavi la lettera iniziale delle parole presenti, minuscola + - come valore il numero di parole che contengono quella lettera ignorando la differenza tra minuscole e maiuscole + +Esempio: +text = 'sOtto lA panca La caPra Canta Sopra LA Panca La CaPra crepa' +expected = { 's':2, 'l':4, 'p':6, 'c':6} +''' + +def func2(text): + pass + lower = text.lower().split() + diz = { parola[0]: 0 for parola in lower } + for parola in lower: + for carattere in diz: + if carattere in parola: + diz[carattere] += 1 + return diz + +#text = 'sOtto lA panca La caPra Canta Sopra LA Panca La CaPra crepa' +#print(func2(text)) + +# ---------------------------- FUNC 3 ---------------------------- # +''' +Func 3: 4 punti +Definite la funzione func3(textfile_in, textfile_out) che riceve come argomento: +- textfile_in: il percorso di un file di testo da leggere +- textfile_out: il percorso di un file di testo da creare + +I file indicato da textfile_in contiene dei numeri +float oppure interi, positivi o negativi, separati da spazi. + +La funzione deve leggere i numeri, ordinarli in ordine decrescente di numero di cifre significative, +e in caso di parità in ordine crescente di valore. +(le cifre significative sono quelle che restano ignorando punto e segno) + +Quindi deve scrivere questi numeri ordinati nel file textfile_out, separati da virgola e spazio +Infine la funzione restituisce la quantita' dei numeri letti da textfile_in. + +Esempio: +se il file textfile_in contiene la riga +-23.5 17 -141 +322.7 -3227 +Nel file textfile_out la funzione deve scrivere la riga +-3227, +322.7, -141, -23.5, 17 +e tornare il valore 5 +''' + +def func3(textfile_in, textfile_out): + def criterio(elemento): + cifre = elemento.replace('.','') + cifre = cifre.replace('-', '') + cifre = cifre.replace('+', '') + return -len(cifre), float(elemento) + with open(textfile_in) as FIN: + numeri = FIN.read().split() + numeri.sort(key = criterio) + with open(textfile_out, mode='w') as FOUT: + print(*numeri, sep=', ', file=FOUT) + return len(numeri) + + +# ---------------------------- FUNC 4 ---------------------------- # + +''' +Func 4: 4 punti +Si definisca la funzione func5(filein) che riceve come argomento +- filein: un file di testo contenente una matrice di interi NxM separati da spazi + +e che ritorna la matrice trasposta rispetto alla diagonale secondaria +(ovvero quella che va dall'elemento in alto a destra a quello in basso a sinistra) +rappresentata come lista di liste. + +Esempio: +se il file filein contiene la matrice +1 2 3 4 +5 6 7 8 +9 10 11 12 +la funzione dovrà tornare la matrice riflessa rispetto alla diagonale 4-9, come lista di liste +[[12, 8, 4], + [11, 7, 3], + [10, 6, 2], + [ 9, 5, 1]] +''' +def func4(input_filename): + pass + with open(input_filename) as FIN: + M = [ list(map(int, riga.split())) for riga in FIN ] + W = len(M[0]) + H = len(M) + return [ [ M[H-1-y][W-1-x] + for y in range(H)] + for x in range(W)] + +# ---------------------------- FUNC 5 ---------------------------- # + +''' +Func 5: 8 punti +Si definisca la funzione func5(txt_input, width, height, png_output) che riceve come argomenti +- txt_input: il percorso di un file che contiene un elenco di figure da disegnare +- width: larghezza in pixel dell'immagine da creare +- height: altezza in pixel dell'immagine da creare +- png_output: il percorso di una immagine PNG che dovete creare, contenente le figure + +La funzione deve creare una immagine a sfondo nero e disegnarci sopra tutte le figure +indicate nel file 'txt_input', nell'ordine in cui appaiono nel file. + +Il file txt_file contiene, una per riga, separate da spazi: +- una parola che indica il tipo di figura da disegnare +- le tre componenti R G B del colore da usare +- le coordinate e gli altri parametri necessari a definire la figura +Possono essre presenti 2 tipi di figura: +- diagonale discendente di un quadrato (in direzione -45°) + diagonalDOWN R G B x y L + La diagonale inizia nel punto (x,y), si dirige in BASSO a destra, ed è lunga L pixel +- diagonale ascendente di un quadrato (in direzione +45°) + diagonalUP R G B x y L + La diagonale inizia nel punto (x,y), si dirige in ALTO a destra, ed è lunga L pixel + +Quindi deve salvare l'immagine ottenuta nel file 'png_output' usando la funzione images.save. +Inoltre deve ritornare il numero di diagonali disegnate dei due tipi +come tupla dei due valori (DIAGUP,DIAGDOWN) + +NOTA: va gestito correttamente lo sbordare delle figure dalla immagine, +infatti sono ammesse anche coordinate negative, +e dimensioni o parametro L tali da far sbordare la figura dalla immagine + +Esempio: se il file func5/in_1.txt contiene le 3 righe +diagonalDOWN 0 255 0 -30 -40 110 +diagonalUP 255 0 0 20 100 200 +diagonalUP 0 0 255 10 120 50 + +l'esecuzione della funzione func5('func5/in_1.txt', 50, 100, 'func5/your_image_1.png') +produrrà una figura uguale al file 'func5/expected_1.png' +e tornerà la coppia (2, 1) +''' + +from math import dist +import images +def func5(txt_input, width, height, png_output): + pass + def draw_diagonalDown(img, W, H, x, y, L, color): + for i in range(L): + X,Y = x+i, y+i + if 0 <= X < W and 0 <= Y < H: + img[Y][X] = color + def draw_diagonalUp(img, W, H, x, y, L, color): + for i in range(L): + X,Y = x+i, y-i + if 0 <= X < W and 0 <= Y < H: + img[Y][X] = color + img = [ [(0,0,0)]*width for _ in range(height) ] # immagine vuota, nera + diagUP = diagDOWN = 0 + with open(txt_input) as F: + for line in F: + tipo, *data= line.split() + R, G, B, x, y, L = list(map(int, data)) + if tipo == 'diagonalDOWN': + draw_diagonalDown(img, width, height, x, y, L, (R, G, B)) + diagDOWN += 1 + elif tipo == 'diagonalUP': + draw_diagonalUp( img, width, height, x, y, L, (R, G, B)) + diagUP += 1 + images.save(img, png_output) + return diagUP, diagDOWN + +# print(func5('func5/in_1.txt', 50, 100, 'func5/out_1.png')) + + +# ---------------------------- EX 1 ---------------------------- # + +''' +Esercizio 1 ricorsivo (6 punti): + +Si definisca la funzione es1(root, valori), ricorsiva o che usa funzioni ricorsive, +che riceve in input +- la radice 'root' di un albero n-ario definito da nodi nary_tree.NaryTree +- una lista di interi 'valori' +che modifica distruttivamente l'albero 'root' sommando a tutti i nodi che sono a profondità P +(assumendo che la radice si trovi a profondità 0) il valore che nella lista 'valori' +si trova all'indice P (se esiste, altrimenti restano come sono). + +La funzione deve restituire la somma 'total' di tutti i nodi dell'albero risultante. + +Esempio: + values: [-42, -80, 68, 2, 81, 75, 54, 48, -4, 5] da sommare + root: -7 | -42 + / | | | \ | + -10 -3 -8 -10 -5 | -80 + / \ | | | | + 6 -2 9 7 -9 | +68 + + expected: -49 | + / | | | \ | + -90 -83 -88 -90 -85 | + / \ | | | | + 74 66 77 75 59 | + total = -134 + +ATTENZIONE: definite la funzione ricorsiva a livello esterno, +ovvero con la parola chiave 'def' appoggiata all'inizio della riga. +''' + +from nary_tree import NaryTree + +def ex1(root : NaryTree, valori : list[int]): + return _ex1(root, valori, 0) + +def _ex1(root : NaryTree, valori : list[int], depth : int): + if depth < len(valori): + root.value += valori[depth] + total = root.value + for son in root.sons: + total += _ex1(son, valori, depth + 1) + return total + + +# %% ----------------------------------- EX.2 ----------------------------------- # +''' +Ex2: 4 + 2 points + Implement the ex2(dirin, words) function, recursive or using recursive + functions or methods, having the argument: + - dirin: the path of an existing directory + - words: a list of words + The function will go through dirin and all its subfolders (at any level), + and count the occurrences of the words in the input list in all the + text files (i.e., files having the .txt extension) found in any folder. + A word occurs in a file, if and only if it is separated from the preceding + or following word, if there are, by a space, a tab, or a newline character. + + (5 points) The function returns a list of pairs (word, occ), in which the first + value of each pair is one of the words in the input list and the second + value of the pair is the number of occurrences of that word in the text files. + (+ 3 points) The list is sorted on the number of occurrences of the words + (in descending order); if two or more words have the same number of occurrences, + they are sorted alphabetically (in ascending order). + If a word in the input list never occurs, it still has to be returned + by the function. + + NOTICE 1: you could find useful the functions: os.listdir, os.path.join, + os.path.isfile, os.mkdir, os.path.exists ... + NOTICE 2: it is forbidden to use the os.walk function + + For example, given the folder "ex2" and if words = ["cat", "dog"] + the function returns: [('dog', 11), ('cat', 6)] + +''' + +import os + + +def RecScan(dir, words): + D = {} + items = os.listdir(dir) + for item in items: + if os.path.isfile(dir + "/" + item): + if item[-4:].lower() == ".txt": + fileRef = open(dir + "/" + item, "r", encoding="utf-8") + for line in fileRef: + tokens = line.strip().replace("\t", " ").split(" ") + for token in tokens: + if token in words: + if token not in D: + D[token] = 1 + else: + D[token] += 1 + fileRef.close() + else: + resD = RecScan(dir + "/" + item, words) + for k in resD: + if k not in D: + D[k] = resD[k] + else: + D[k] += resD[k] + return D + + +def ex2(dirin, words): + D = RecScan(dirin, words) + result = [] + for k in D: + result.append((k, D[k])) + return sorted(result, key=lambda x: x[1], reverse=True) + +###################################################################################### + +if __name__ == '__main__': + pass + from random import randint, choice + def creadiagonale(maxxy): + UD = choice(['diagonalUP', 'diagonalDOWN']) + R = randint(0, 255) + G = randint(0, 255) + B = randint(0, 255) + x = randint(-50, maxxy) + y = randint(-50, maxxy) + L = randint(100, maxxy) + return UD, R, G, B, x, y, L + ID = 4 + N = 55 + FN = f'func5/in_{ID}.txt' + with open(FN, mode='w') as F: + for _ in range(N): + print(*creadiagonale(500), sep=' ', file=F) + + diff --git a/Esami/2022-2023/Esame-7/program.vuoto.py b/Esami/2022-2023/Esame-7/program.vuoto.py new file mode 100644 index 0000000..df403e7 --- /dev/null +++ b/Esami/2022-2023/Esame-7/program.vuoto.py @@ -0,0 +1,308 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +################################################################################ +################################################################################ +################################################################################ + +""" Operazioni da fare PRIMA DI TUTTO: + 1) Salvare il file come program.py + 2) Assegnare le variabili sottostanti con il tuo + NOME, COGNOME, NUMERO DI MATRICOLA + +Per superare l'esame è necessario: + - !!!riempire le informazioni personali nelle variabili qui sotto!!! + - AND risolvere almeno 1 esercizio di tipo ex (problema ricorsivo) + - AND risolvere almeno 3 esercizi di tipo func + - AND ottenere un punteggio maggiore o uguale a 18 + +Il punteggio finale è la somma dei punteggi dei problemi risolti. +""" +nome = "NOME" +cognome = "COGNOME" +matricola = "MATRICOLA" + +################################################################################ +################################################################################ +################################################################################ +# ---------------------------- DEBUG SUGGESTIONS ----------------------------- # +# To run only some of the tests, you can comment the entries with which the +# 'tests' list is assigned at the end of grade.py +# +# To debug recursive functions you can turn off the recursive test setting +# DEBUG=True in the file grade.py +# +# DEBUG=True turns on also the STACK TRACE that allows you to know which +# line number in program.py generated the error. +################################################################################ + +# ---------------------------- FUNC 1 ---------------------------- # + +''' +Func 1: 2 punti +Si definisca la funzione func1(diz1, diz2) che riceve come argomenti +due dizionari che hanno chiavi intere e valori liste di stringhe. La +funzione deve tornare il dizionario che contiene le sole chiavi in +comune ad entrambi i dizionari. I valori associati a ciascuna chiave +sono quelli che appaiono in una sola delle due liste associate a +quella chiave nei due dizionari. Questi valori, senza ripetizioni, +vanno ordinati in ordine di lunghezza decrescente ed in caso di parità +in ordine alfabetico crescente. + +Esempio: +diz1 = { 1: ['a', 'bc', 'a'], 2: ['b', 'cr', 'e'], 3: ['a', 'qrt', 'st'] } +diz2 = { 1: ['a', 'cd', 'f'], 5: ['b', 'cr', 'e'], 3: ['a', 'bn', 'c'] } +il risultato sarà { 1: ['bc', 'cd', 'f'], 3: ['qrt', 'bn', 'st', 'c'] } +''' + +def func1(diz1, diz2): + pass + +#diz1 = { 1: ['a', 'bc', 'a'], 2: ['b', 'cr', 'e'], 3: ['a', 'qrt', 'st'] } +#diz2 = { 1: ['a', 'cd', 'f'], 5: ['b', 'cr', 'e'], 3: ['a', 'bn', 'c'] } +#print(func1(diz1,diz2)) + +#%% ---------------------------- FUNC 2 ---------------------------- # + +''' +Func 2: 2 punti + +Si definisca la funzione func2(text) che riceve come argomento: +- text: una stringa formata da parole separate da spazi +e che ritorna un dizionario che ha: + - come chiavi la lettera iniziale delle parole presenti, minuscola + - come valore il numero di parole che contengono quella lettera + ignorando la differenza tra minuscole e maiuscole + +Esempio: +text = 'sOtto lA panca La caPra Canta Sopra LA Panca La CaPra crepa' +expected = { 's':2, 'l':4, 'p':6, 'c':6} +''' + +def func2(text): + pass + +#text = 'sOtto lA panca La caPra Canta Sopra LA Panca La CaPra crepa' +#print(func2(text)) + +#%% ---------------------------- FUNC 3 ---------------------------- # +''' +Func 3: 4 punti +Definite la funzione func3(textfile_in, textfile_out) che riceve come argomento: +- textfile_in: il percorso di un file di testo da leggere +- textfile_out: il percorso di un file di testo da creare + +Il file indicato da textfile_in contiene dei numeri +float oppure interi, positivi o negativi, separati da spazi. + +La funzione deve leggere i numeri, ordinarli in ordine decrescente di +di caratteri numerici presenti e in caso di parità in ordine crescente di valore. + +Quindi deve scrivere questi numeri ordinati nel file textfile_out, +separati da virgola e spazio. Infine la funzione restituisce la +quantità di numeri letti da textfile_in. + +Esempio: +se il file textfile_in contiene la riga + +-23.5 17 -141 +322.7 -3227 + +Nel file textfile_out la funzione deve scrivere la riga + +-3227, +322.7, -141, -23.5, 17 + +e tornare il valore 5 +''' + +def func3(textfile_in, textfile_out): + pass + + +#print(func3('func3/in_1.txt', 'func3/out_1.txt')) + +#%% ---------------------------- FUNC 4 ---------------------------- # + +''' +Func 4: 4 punti +Si definisca la funzione func5(filein) che riceve come argomento +- filein: un file di testo contenente una matrice di interi NxM + separati da spazi + +e che ritorna la matrice trasposta rispetto alla diagonale secondaria, +ovvero quella che va dall'elemento in alto a destra a quello in basso +a sinistra. La matrice da restituire e' rappresentata come lista di liste. + +Esempio: +se il file filein contiene la matrice +1 2 3 4 +5 6 7 8 +9 10 11 12 + +la funzione dovrà tornare la matrice riflessa rispetto alla diagonale 4-9, +come lista di liste +[[12, 8, 4], + [11, 7, 3], + [10, 6, 2], + [ 9, 5, 1]] +''' + + +def func4(input_filename): + pass + +# print(func4('func4/in_1.txt')) + +#%% ---------------------------- FUNC 5 ---------------------------- # + +''' +Func 5: 8 punti +Si definisca la funzione func5(txt_input, width, height, png_output) che riceve come argomenti + +- txt_input: il percorso di un file che contiene un elenco di figure da disegnare +- width: larghezza in pixel dell'immagine da creare +- height: altezza in pixel dell'immagine da creare +- png_output: il percorso di una immagine PNG che dovete creare, contenente le figure + +La funzione deve creare una immagine a sfondo nero e disegnarci sopra +tutte le figure indicate nel file 'txt_input', nell'ordine in cui +appaiono nel file. + +Il file txt_file contiene, una per riga, separate da spazi: +- una parola che indica il tipo di figura da disegnare +- le tre componenti R G B del colore da usare +- le coordinate e gli altri parametri necessari a definire la figura + +Possono essre presenti 2 tipi di figura: +- diagonale discendente di un quadrato (in direzione -45°) + diagonalDOWN R G B x y L + La diagonale inizia nel punto (x,y), si dirige in BASSO a destra, ed è lunga L pixel +- diagonale ascendente di un quadrato (in direzione +45°) + diagonalUP R G B x y L + La diagonale inizia nel punto (x,y), si dirige in ALTO a destra, ed è lunga L pixel + +Quindi deve salvare l'immagine ottenuta nel file 'png_output' usando la funzione images.save. +Inoltre deve ritornare il numero di diagonali disegnate dei due tipi +come tupla dei due valori (DIAGUP,DIAGDOWN) + +NOTA: va gestito correttamente lo sbordare delle figure dalla +immagine, infatti sono ammesse anche coordinate negative, e dimensioni +o parametro L tali da far sbordare la figura dalla immagine + +Esempio: se il file func5/in_1.txt contiene le 3 righe +diagonalDOWN 0 255 0 -30 -40 110 +diagonalUP 255 0 0 20 100 200 +diagonalUP 0 0 255 10 120 50 + +l'esecuzione della funzione func5('func5/in_1.txt', 50, 100, 'func5/your_image_1.png') +produrrà una figura uguale al file 'func5/expected_1.png' +e tornerà la coppia (2, 1) +''' + + +import images + +def func5(txt_input, width, height, png_output): + pass + +#print(func5('func5/in_1.txt', 50, 100, 'func5/out_1.png')) + + +#%% ---------------------------- EX 1 ---------------------------- # + +''' +Esercizio 1 ricorsivo (6 punti): + +Si definisca la funzione es1(root, valori), ricorsiva o che usa funzioni ricorsive, +che riceve in input: +- la radice 'root' di un albero n-ario definito da nodi nary_tree.NaryTree +- una lista di interi 'valori' +che modifica distruttivamente l'albero 'root' sommando a tutti i nodi +che sono a profondità P il valore che nella lista 'valori' si trova +all'indice P, se esiste, altrimenti restano come sono. Si assuma che +la radice si trovi a profondità 0. + +La funzione deve restituire la somma 'total' di tutti i nodi +dell'albero risultante. + +ATTENZIONE: definite la funzione ricorsiva a livello esterno, +ovvero con la parola chiave 'def' appoggiata all'inizio della riga. + +Esempio: + values: [-42, -80, 68, 2, 81, 75, 54, 48, -4, 5] da sommare + root: -7 | -42 + / | | | \ | + -10 -3 -8 -10 -5 | -80 + / \ | | | | + 6 -2 9 7 -9 | +68 + + expected: -49 | + / | | | \ | + -90 -83 -88 -90 -85 | + / \ | | | | + 74 66 77 75 59 | + total = -134 + +''' + +from nary_tree import NaryTree + + +def ex1(root : NaryTree, valori : list[int]): + pass + + +# %% ----------------------------------- EX.2 ----------------------------------- # + + +''' +Ex2: 3 + 3 points +Definite la funzione ex2(dirin, words), ricorsiva o che usa funzioni o metodi ricorsivi, +che riceve come argomenti: + - dirin: il path di una directory + - words: una lista di parole + +La funzione esplora dirin e tutte le sue sottodirectory (a tutti i +livelli) e conta il numero di occorrenze delle words nei file di testo +(quelli che hanno '.txt' come estensione) a tutti i livelli. Una +parola appare in un file se è separata dalla precedente o dalla +seguente da almeno uno spazio, tab, o newline. + +(3 points) +La funzione torna una lista di coppie (word, occorrenze) in cui il +primo elemento è la word ed il secondo è il numero di occorrenze +trovate. Se una word non appare in alcun file il suo numero di +occorrenze è 0. + +(+ 3 points) +Ordinate la lista di coppie in ordine decrescente di numero di +occorrenze ed in caso di parità in ordine alfabetico crescente. + +NOTA 1: potete usare le funzioni: os.listdir, os.path.join, +os.path.isfile, os.mkdir, os.path.exists ... +NOTA 2: è proibito usare la funzione os.walk +NOTA 3: usate il carattere '/' come separatore dei path +(che funzione sia in Windows che su MacOS o Linux) + +Esempio: +se il path dirin è "ex2" e le words = ["cat", "dog"] +la funzione ritorna: [('dog', 10), ('cat', 5)] +''' + +import os + + +def ex2(dirin, words): + pass + +###################################################################################### + +if __name__ == '__main__': + # Scrivi qui i tuoi test addizionali, attenzione a non sovrascrivere + # gli EXPECTED! + print('*' * 50) + print('ITA\nDevi eseguire il grade.py se vuoi debuggare con il grader incorporato.') + print( + 'Altrimenii puoi inserire qui del codice per testare le tue funzioni ma devi scriverti i casi che vuoi testare') + print('*' * 50) + + diff --git a/Esami/2022-2023/Esame-7/testlib.py b/Esami/2022-2023/Esame-7/testlib.py new file mode 100644 index 0000000..a9dc38e --- /dev/null +++ b/Esami/2022-2023/Esame-7/testlib.py @@ -0,0 +1,254 @@ +import argparse, csv, glob, time, pprint, json +#from grade import my_print as print +# import deepdiff + +msg_ok = '[OK]: \t {points} point(s)\t {duration:.3f} ms\n' +msg_0points = 'error: {points} points\t {duration:.3f} ms\n' +msg_err = 'error: {exname}\n\t{exmsg}\n' + +COL = {'RED': '\u001b[31m', + 'RST': '\u001b[0m', + 'GREEN': '\u001b[32m', + 'YELLOW' : '\u001b[33m', + 'BOLD' : '\033[1m', + 'ENDC' : '\033[0m'} + +def my_decorator(func): + def wrapped_func(*args, **kwargs): + col = '' + if any(err in args[0] for err in ['[OK]', 'Correct']): + col = COL['BOLD']+COL['GREEN'] + if any(err in args[0] for err in ['error', 'Error', 'ERROR',]): + col = COL['BOLD']+COL['RED'] + if col: + return func(f'{col}', *args, f'{COL["RST"]}{COL["ENDC"]}', **kwargs, ) + else: + return func(*args, **kwargs, ) + return wrapped_func +my_print = my_decorator(print) + +class NotImplemented(Exception): + pass + + +def run(tests, verbose, logfile=''): + results = [] + for test in tests: + results.append(runOne(test, verbose, logfile)) + return results + + +def runOne(test, verbose, logfile='', stack_trace=False): + try: + doc=test.__doc__ or '' + print(f'Running {test.__name__}\t{doc}') + start = time.time() + v = test() + end = time.time() + if v: + print(msg_ok.format( + points=v, + duration=(end-start)*1000)) + else: + print(msg_0points.format( + points=v, + duration=(end-start)*1000)) + except NotImplemented: + print("Not implemented: (None returned) ", test.__name__) + v = 0 + except Exception as e: + import traceback + print(msg_err.format( + exname=e.__class__.__name__, + exmsg=str(e) if str(e) else '')) + if stack_trace: + print('*'*50+'[BEGIN STACK TRACE]'+'*'*50) + traceback.print_exc() + print('*'*50+'[END STACK TRACE]'+'*'*50) + v = 0 + result = test.__name__, v + log([result], logfile) + return result + + +def description(tests): + for test in tests: + print(test.__name__ + ': ' + test.__help__) + + +def emptyLog(logfile): + if logfile: + with open(logfile,'w',newline='',encoding='utf8') as f: + f.truncate() + + +def log(results, logfile): + if logfile: + with open(logfile, 'a', newline='', encoding='utf8') as f: + writer = csv.writer(f) + writer.writerows(results) + + +def check10(a, b, params=None, expl=''): + msg = '' + if params: + msg += '\twhen input={}'.format(params) + msg += '\n\t\t%r != %r' % (a, b) + if expl: + msg += "\t<- correct %s value" % expl + assert a == b, msg + + +def check(a, b, params=None, expl='', other=None): + msg = '' + if params: + msg += 'when input={}'.format(params) + if other: + msg += '\n\t\t%r != %r ' % (other[0], other[1]) + else: + msg += '\n\t%r \n\t!= \n\t%r\n\n' % (a, b) + if expl: + msg += "\t<- %s\n\n\n" % expl + if (a == None) | (a == type(None)): + raise NotImplemented() + # if a != b: + # pprint.pprint(deepdiff.DeepDiff(a, b)) + assert a == b, msg + + +def check1(a, b, params=None, expl='', other=None): + msg = '' + if params: + msg += 'when input={}'.format(params) + if other: + msg += '\n\t\t%r != %r ' % (other[0], other[1]) + else: + msg += '\n\t%r \n\t!= \n\t%r\n\n' % (a, b) + if expl: + msg += "\t<- %s\n\n\n" % expl + if a == None: + raise NotImplemented() + assert a == b, msg + + +def check_text_file(a,b): + with open(a, 'rU', encoding='utf8') as f: txt_a = f.read() + with open(b, 'rU', encoding='utf8') as f: txt_b = f.read() + lines_a = [l.strip() for l in txt_a.splitlines()] + lines_b = [l.strip() for l in txt_b.splitlines()] + assert lines_a == lines_b, 'text differ: ' + a + ' ' + b + + +def check_json_file(a,b, params=None, expl='', other=''): + with open(a, 'rU', encoding='utf8') as f: da = json.load(f) + with open(b,' rU', encoding='utf8') as f: db = json.load(f) + check(da, db, params, expl, other) + + +def image_load(filename): + '''Load the PNG image from the PNG file under 'filename', + convert it into tuple-matrix format and return it''' + import png + with open(filename, 'rb') as f: + # Read the image as an RGB-file with 256 colours (without transparency) + reader = png.Reader(file=f) + w, h, png_img, _ = reader.asRGB8() + # Convert the list of lists into tuples. Notice that the PNG colours + # are triples in the png_img array, so we read them by a step of three. + w *= 3 + return [[(line[i], line[i + 1], line[i + 2]) + for i in range(0, w, 3)] + for line in png_img] + return img + + +def check_img_file(a, b): + img_a = image_load(a) + img_b = image_load(b) + ha = len(img_a) + hb = len(img_b) + assert ha == hb, 'Images have different heights: {} != {}'.format(ha, hb) + assert ha > 0 and hb > 0, 'An image has a height of 0: {} {}'.format(ha, hb) + wa = len(img_a[0]) + wb = len(img_b[0]) + assert wa == wb, 'Images have different widths: {} != {}'.format(wa, wb) + assert wa > 0 and wb > 0, 'An image has a height of 0: {} {}'.format(wa, wb) + for y in range(ha): + for x in range(wa): + ca = img_a[y][x] + cb = img_b[y][x] + assert ca == cb, \ + 'Images differ at coordinates {},{} : {} != {}'.format( + x, y, ca, cb) + + +def runtests(tests, verbose=True, logfile='', stack_trace=False): + if logfile: + emptyLog(logfile) + for test in tests: + runOne(test, verbose, logfile, stack_trace) + with open(logfile, newline='', encoding='utf8') as f: + tot = 0 + reader = csv.reader(f) + for row in reader: + tot += float(row[1]) + print('Total score:', tot) + else: + for test in tests: + runOne(test, verbose, logfile, stack_trace) + + +import tempfile, os, os.path + + +class randomized_filename: + + def __init__(self, filename): + name, ext = filename.split('.') + self.filename = filename + self.randomized = next(tempfile._get_candidate_names()) + '.' + ext + + def __enter__(self): + if os.path.isfile(self.filename): + print(self.filename, ' -> ', self.randomized) + os.rename(self.filename, self.randomized) + return self.randomized + + def __exit__(self, type, value, traceback): + if os.path.isfile(self.randomized): + print(self.filename, ' <- ', self.randomized) + os.rename(self.randomized, self.filename) + +def check_exam_constraints(): + grades = {} + total = 0 + with open('grade.csv') as F: + for line in F: + test, points = line.split(',') + _, name, *_ = test.split('_') + #if name == 'personal': continue + total += float(points) + grades[name] = grades.get(name, 0) + float(points) + #%% Constraint for the exam + constraint1 = len([name for name,grade in grades.items() if grade>0 and name.startswith('func')]) >= 3 + constraint2 = len([name for name,grade in grades.items() if grade>0 and name.startswith('ex')]) >= 1 + constraint3 = total >= 18 + constraint4 = 'personal' in grades and grades['personal'] == 0 + constraint5 = all((constraint1, constraint2, constraint3, constraint4)) + if not constraint4: + print(f'YOU HAVE NOT FILLED YOUR PERSONAL DETAILS: {COL["RED"]}EXAM NOT PASSED{COL["RST"]}') + elif not constraint1: + print(f'YOU HAVE NOT PASSED AT LEAST 3 FUNC EXERCISES: {COL["RED"]}EXAM NOT PASSED{COL["RST"]}') + elif not constraint2: + print(f'YOU HAVE NOT PASSED AT LEAST 1 RECURSIVE EXERCISE: {COL["RED"]}EXAM NOT PASSED{COL["RST"]}') + elif not constraint3: + print(f'THE FINAL GRADE IS LESS THAN 18: {COL["RED"]}EXAM NOT PASSED{COL["RST"]}') + else: + print(f"YOU HAVE {COL['GREEN']}PASSED{COL['RST']} THE EXAM WITH {COL['BOLD']+COL['GREEN']}{total}{COL['RST']} POINTS") + ENDC = '\033[0m' + BOLD = '\033[1m' + print(f"Personal info present: {COL['BOLD']} {COL['GREEN'] if constraint4 else COL['RED']} {constraint4}{COL['RST']}{COL['ENDC']}") + print(f"Three func problems solved: {COL['BOLD']} {COL['GREEN'] if constraint1 else COL['RED']} {constraint1}{COL['RST']}{COL['ENDC']}") + print(f"One ex problem solved: {COL['BOLD']} {COL['GREEN'] if constraint2 else COL['RED']} {constraint2}{COL['RST']}{COL['ENDC']} ") + print(f"Total >= 18: {COL['BOLD']} {COL['GREEN'] if constraint3 else COL['RED']} {constraint3}{COL['RST']}{COL['ENDC']}") + print(f"Exam passed: {COL['BOLD']} {COL['GREEN'] if constraint5 else COL['RED']} {constraint5}{COL['RST']}{COL['ENDC']}")