Глава 6.2. Вложени цикли – изпитни задачи
В предходната глава разгледахме вложените цикли и как да ги използване за рисуване на различни фигури на конзолата. Научихме се как да отпечатваме фигури с различни размери, измисляйки подходяща логика на конструиране с използване на единични и вложени for цикли в комбинация с различни изчисления и програмна логика:
for r in range(5):
print("*", end="")
for c in range(5):
print(" *", end="")
print("")
Запознахме се и с оператора *, който дава възможност даден стринг да се печата определен от нас брой пъти:
print_me = ('abc' * 2)
Изпитни задачи
Сега нека решим заедно няколко изпитни задачи, за да затвърдим наученото и да развием още алгоритмичното си мислене.
Задача: чертане на крепост
Да се напише програма, която приема цяло число n и чертае крепост с ширина 2 * n колони и височина n реда като в примерите по-долу. Лявата и дясната колона във вътрешността си са широки n / 2.
Входни данни
Входът на програмата се състои от един елемент (аргумент) - цяло число n в интервала [3 … 1000].
Изходни данни
Да се отпечатат на конзолата n текстови реда, изобразяващи крепостта, точно както в примерите.
Примерен вход и изход
| Вход | Изход | Вход | Изход |
|---|---|---|---|
| 3 | /^\/^\| |\_/\_/ |
4 | /^^\/^^\| || |\__/\__/ |
| Вход | Изход | Вход | Изход |
|---|---|---|---|
| 5 | /^^\__/^^\| || || __ |\__/ \__/ |
8 | /^^^^\____/^^^^\| || || || || || ____ |\____/ \____/ |
Насоки и подсказки
От условието на задачата виждаме, че входните данни ще се състоят само от един ред, който ще съдържа в себе си едно цяло число в интервала [3 … 1000]. По тази причина ще конвертираме прочетената стрингова стойност към тип int:

След като вече сме декларирали и инициализирали входните данни, ще разделим крепостта на три части:
- покрив
- тяло
- основа
От примерите можем да разберем, че покривът е съставен от две кули и междинна част. Всяка кула се състои от начало /, среда ^ и край \.
По условие лявата и дясната колона във вътрешността си са широки n / 2, следователно можем да отделим тази стойност в отделна променлива, като внимаваме, че ако като входни данни имаме нечетно число, при деление на две резултатът ще е реално число с цяла и дробна част. Тъй като в този случай ни трябва само цялата част (в условието на задачата виждаме, че при вход 3 броят на ^ във вътрешността на колоната е 1, а при вход 5 е 3), можем да я отделим с метода math.trunc(…) и да запазим само нейната стойност в новата ни променлива:

| Добра практика е винаги, когато видим, че имаме израз, чиято стойност ще използваме повече от един път, да запазваме стойността му в променлива. Така от една страна, кодът ни ще бъде по-лесно четим, от друга страна, ще бъде по-лесно да поправим евентуални грешки в него, тъй като няма да се налага да търсим поотделно всяка употреба на израза. |
Декларираме и втора променлива, в която ще пазим стойността на частта между двете кули. Знаем, че по условие общата ширина на крепостта е n * 2. Освен това имаме и две кули с по една наклонена черта за начало и край (общо 4 знака) и ширина colSize. Следователно, за да получим броя на знаците в междинната част, трябва да извадим размера на кулите от ширината на цялата крепост: 2 * n - 2 * colSize - 4:

За да отпечатаме на конзолата покрива, ще използваме метода format(…) в комбинация с оператора *, който съединява даден символ в низ n на брой пъти:

\ е специален символ в езика Python и използвайки само него в метода print(…), конзолата няма да го разпечата, затова с \\ показваме на конзолата, че искаме да отпечатаме точно този символ, без да се интерпретира като специален (екранираме го, на английски се нарича “character escaping”). |
Тялото на крепостта се състои от начало |, среда (празно място) и край |. Средата от празно място е с големина 2 * n - 2. Броят на редовете за стени, можем да определим от дадените ни примери - n - 3:

За да нарисуваме предпоследния ред, който е част от основата, трябва да отпечатаме начало |, среда (празно място)_(празно място) и край |. За да направим това, можем да използваме отново вече декларираните от нас променливи col_size и mid_size, защото от примерите виждаме, че са равни на броя _ в покрива:

Добавяме към стойността на празните места + 1, защото в примерите имаме едно празно място повече.
Структурата на основата на крепостта е еднаква с тази на покрива. Съставена е от две кули и междинна част. Всяка една кула има начало \, среда _ и край /:

Тестване в Judge системата
Тествайте решението си тук: https://judge.softuni.org/Contests/Practice/Index/1056#0.
Задача: пеперуда
Да се напише програма, която приема цяло число n и чертае пеперуда с ширина 2 * n - 1 колони и височина 2 * (n - 2) + 1 реда като в примерите по-долу. Лявата и дясната ѝ част са широки n - 1.
Входни данни
Входът се състои от един елемент (аргумент) - цяло число n [3 … 1000].
Изходни данни
Да се отпечатат на конзолата 2 * (n - 2) + 1 текстови реда, изобразяващи пеперудата, точно както в примерите.
Примерен вход и изход
| Вход | Изход | Вход | Изход |
|---|---|---|---|
| 3 | *\ /* @ */ \* |
5 | ***\ /***---\ /---***\ /*** @ ***/ \***---/ \---***/ \*** |
| Вход | Изход |
|---|---|
| 7 | *****\ /*****-----\ /-----*****\ /*****-----\ /-----*****\ /***** @ *****/ \*****-----/ \-----*****/ \*****-----/ \-----*****/ \***** |
Насоки и подсказки
Аналогично на предходната задача виждаме от условието, че входните данни ще се състоят само от едно цяло число в интервала [3 … 1000]. По тази причина ще конвертираме прочетената стойност към тип int:

Можем да разделим фигурата на 3 части - горно крило, тяло и долно крило. За да начертаем горното крило на пеперудата, трябва да го разделим на части - начало *, среда \ / и край *. След разглеждане на примерите можем да кажем, че горното крило на пеперудата е с големина n - 2:

За да нарисуваме горното крило правим цикъл, който да се повтаря half_row_size пъти:

От примерите можем също така да забележим, че на четен ред имаме начало *, среда \ / и край *, а на нечетен - начало -, среда \ / и край -. Следователно при всяка итерация на цикъла трябва да направим if-else проверка дали редът, който печатаме, е четен или нечетен. От примерите, дадени в условието, виждаме, че броят на звездичките и тиретата на всеки ред също е равен на n - 2, т. е. за тяхното отпечатване отново можем да използваме променливата half_row_size:

За да направим тялото на пеперудата е необходимо да отпечатаме на конзолата точно един ред. Структурата на тялото е с начало (празно място), среда @ и край (празно място). От примерите виждаме, че броят на празните места е n - 1:

Остава да отпечатаме на конзолата и долното крило, което е аналогично на горното крило: единствено трябва да разменим местата на наклонените черти.
Тестване в Judge системата
Тествайте решението си тук: https://judge.softuni.org/Contests/Practice/Index/1056#1.
Задача: знак "Стоп"
Да се напише програма, която приема цяло число n и чертае предупредителен знак STOP с размери като в примерите по-долу.
Входни данни
Входът е състоящ се от един елемент (аргумент)- цяло число n в интервала [3 … 1000].
Изходни данни
Да се отпечатат на конзолата текстови редове, изобразяващи предупредителния знак STOP, точно както в примерите.
Примерен вход и изход
| Вход | Изход | Вход | Изход |
|---|---|---|---|
| 3 | ...._______.......//_____\\.....//_______\\...//_________\\.//___STOP!___\\\\___________//.\\_________//...\\_______//.. |
6 | ......._____________.............//___________\\...........//_____________\\.........//_______________\\.......//_________________\\.....//___________________\\...//_____________________\\.//_________STOP!_________\\\\_______________________//.\\_____________________//...\\___________________//.....\\_________________//.......\\_______________//.........\\\___________//..... |
| Вход | Изход |
|---|---|
| 7 | ........_______________...............//_____________\\.............//_______________\\...........//_________________\\.........//___________________\\.......//_____________________\\.....//_______________________\\...//_________________________\\.//___________STOP!___________\\\\___________________________//.\\_________________________//...\\_______________________//.....\\_____________________//.......\\___________________//.........\\_________________//...........\\_______________//...... |
Насоки и подсказки
Както и при предходните задачи, входните данни ще бъдат прочетени само от един ред, който ще съдържа в себе си едно цяло число в интервала [3 … 1000]:

Можем да разделим фигурата на 3 части - горна, средна и долна. Горната част се състои от две подчасти - начален ред и редове, в които знака се разширява. Началния ред е съставен от начало ., среда _ и край .. След разглеждане на примерите можем да кажем, че началото е с големина n + 1 и е добре да отделим тази стойност в отделна променлива. Трябва да създадем и втора променлива, в която ще пазим стойността на средата на началния ред с големина 2 * n + 1:

След като вече сме декларирали и инициализирали двете променливи, можем да отпечатаме на конзолата началния ред:

За да начертаем редовете, в които знака се "разширява", трябва да създадем цикъл, който да се завърти n брой пъти. Структурата на един ред се състои от начало ., // + среда _ + \\ и край .. За да можем да използваме отново създадените променливи, трябва да намалим dots с 1 и underscores с 2, защото ние вече сме отпечатали първия ред, а точките и долните черти в горната част от фигурата на всеки ред намаляват:

На всяка следваща итерация началото и краят намаляват с 1, а средата се увеличава с 2:

Средната част от фигурата има начало // + _, среда STOP! и край _ + \\. Броят на долните черти _ е (underscores - 5) / 2:

Долната част на фигурата, в която знака се смалява, можем да направим като отново създадем цикъл, който да се завърти n брой пъти. Структурата на един ред е начало . + \\, среда _ и край // + .. Броят на точките при първата итерация на цикъла трябва да е 0 и на всяка следваща да се увеличава с едно. Следователно можем да кажем, че броят на точките в долната част от фигурата е равен на i. За да работи нашата програма правилно, трябва на всяка итерация от цикъла да намаляваме броя на _ с 2:

Тестване в Judge системата
Тествайте решението си тук: https://judge.softuni.org/Contests/Practice/Index/1056#2.
Задача: стрелка
Да се напише програма, която приема цяло нечетно число n и чертае вертикална стрелка с размери като в примерите по-долу.
Входни данни
Входът се състои от цяло нечетно число n (аргумент) в интервала [3 … 79].
Изходни данни
Да се отпечата на конзолата вертикална стрелка, при която "#" (диез) очертава стрелката, а "." - останалото.
Примерен вход и изход
| Вход | Изход | Вход | Изход |
|---|---|---|---|
| 3 | .###..#.#.##.##.#.#...#.. |
5 | ..#####....#...#....#...#....#...#..###...###.#.....#...#...#.....#.#.......#.... |
| Вход | Изход |
|---|---|
| 9 | ....#########........#.......#........#.......#........#.......#........#.......#........#.......#........#.......#........#.......#....#####.......#####.#.............#...#...........#.....#.........#.......#.......#.........#.....#...........#...#.............#.#...............#........ |
Насоки и подсказки
От условието на задачата виждаме, че входните данни ще бъдат прочетени само от един ред, който ще съдържа в себе си едно цяло число в интервала [3 … 79]. По тази причина отново ще конвертираме прочетената стойност към тип int:

Можем да разделим фигурата на 3 части - горна, средна и долна. Горната част се състои от две подчасти - начален ред и тяло на стрелката. От примерите виждаме, че броят на външните точки в началния ред и в тялото на стрелката са (n - 1) / 2. Тази стойност можем да запишем в променлива outer_dots:

Броят на вътрешните точки в тялото на стрелката е (n - 2). Трябва да създадем променлива с име inner_dots, която ще пази тази стойност:

От примерите можем да видим структурата на началния ред. Трябва да използваме декларираните и инициализирани от нас променливи outer_dots и n, за да отпечатаме началния ред:

За да нарисуваме на конзолата тялото на стрелката, трябва да създадем цикъл, който да се повтори n - 2 пъти:

Средата на фигурата е съставена от начало #, среда . и край #. Броят на # е равен на outer_dots и за това можем да използваме отново същата променлива:

За да начертаем долната част на стрелката, трябва да зададем нови стойности на двете променливи outer_dots и inner_dots.

При всяка итерация outer_dots се увеличава с 1, а inner_dots намалява с 2. Забелязваме, че тъй като на предпоследния ред стойността на inner_dots ще е 1 и при последвала итерация на цикъла тя ще стане отрицателно число. Тъй като оператора * не може да съедини символ 0 или отрицателен брой пъти в низ, няма да се изведе нищо на конзолата. Един вариант да избегнем това е да отпечатаме последния ред на фигурата отделно. Височината на долната част на стрелката е n - 1, следователно цикълът, който ще отпечата всички редове без последния, трябва да се завърти n - 2 пъти:

Последният ред от нашата фигура е съставен от начало ., среда # и край .. Броят на . е равен на outer_dots:

Тестване в Judge системата
Тествайте решението си тук: https://judge.softuni.org/Contests/Practice/Index/1056#3.
Задача: брадва
Да се напише програма, която приема цяло число n и чертае брадва с размери, показани по-долу. Ширината на брадвата е 5 * n колони.
Входни данни
Входът се състои от един елемент (аргумент) - цяло число n в интервала [2..42].
Изходни данни
Да се отпечата на конзолата брадва, точно както е в примерите.
Примерен вход и изход
| Вход | Изход | Вход | Изход |
|---|---|---|---|
| 2 | ------**--------*-*-*******-*-------***- |
5 | ---------------**-----------------------*-*----------------------*--*---------------------*---*--------------------*----*----****************----*----****************----*-------------------*----*------------------********--- |
| Вход | Изход |
|---|---|
| 8 | ------------------------**--------------------------------------*-*-------------------------------------*--*------------------------------------*---*-----------------------------------*----*----------------------------------*-----*---------------------------------*------*--------------------------------*-------*-------*************************-------*-------*************************-------*-------*************************-------*-------*************************-------*-------------------------------*-------*------------------------------*---------*----------------------------*-----------*--------------------------***************---- |
Насоки и подсказки
От условието на задачата виждаме, че входните данни ще бъдат прочетени само от един ред, който ще съдържа в себе си едно цяло число в интервала [2 … 42]. По тази причина ще използваме тип int. След което, за решението на задачата е нужно първо да изчислим големината на тиретата от ляво, средните тирета, тиретата от дясно и цялата дължина на фигурата:

След като сме декларирали и инициализирали променливите, можем да започнем да изчертаваме фигурата като започнем с горната част. От примерите можем да разберем каква е структурата на първия ред и да създадем цикъл, който се повтаря n на брой пъти. При всяка итерация от цикъла средните тирета се увеличават с 1, а тиретата отдясно се намаляват с 1:

Сега следва да нарисуваме дръжката на брадвата. За да можем да използваме отново създадените променливи при чертането на дръжката на брадвата, трябва да намалим средните тирета с 1, а тези отдясно и отляво да увеличим с 1.

Дръжката на брадвата можем да нарисуваме, като завъртим цикъл, който се повтаря n / 2 пъти. Можем да отделим тази стойност в отделна променлива, като внимаваме, че ако като входни данни имаме нечетно число, при деление на 2 резултатът ще е реално число с цяла и дробна част. Тъй като в този случай ни трябва само цялата част (от условието на задачата виждаме, че при вход 5 височината на дръжката на брадвата е 2), можем да използваме метода math.trunc(…), с който да запазим само нейната стойност в новата ни променлива axe_height. От примерите можем да разберем, каква е структурата на дръжката:

Долната част на фигурата, трябва да разделим на две подчасти - глава на брадвата и последния ред от фигурата. Главата на брадвата ще отпечатаме на конзолата, като направим цикъл, който да се повтаря axe_height - 1 пъти. На всяка итерация тиретата отляво и тиретата отдясно намаляват с 1, а средните тирета се увеличават с 2:

За последния ред от фигурата, можем отново да използваме трите, вече декларирани и инициализирани променливи left_dashes, middle_dashes, right_dashes.

Тестване в Judge системата
Тествайте решението си тук: https://judge.softuni.org/Contests/Practice/Index/1056#4.