Глава 6.1. Вложени цикли
В настоящата глава ще разгледаме вложените цикли и как да използваме for
цикли за чертане на различни фигурки на конзолата, които се състоят от символи и знаци, разположени в редове и колони на конзолата. Ще използваме единични и вложени цикли (цикли един в друг), изчисления и проверки, за да отпечатваме на конзолата различни фигури по зададени размери.
Видео
Пример: правоъгълник от 10 x 10 звездички
Да се начертае в конзолата правоъгълник от 10 x 10 звездички.
Вход | Изход |
---|---|
(няма) | ********** ********** ********** ********** ********** ********** ********** ********** ********** ********** |
Насоки и подсказки
Как работи примерът? Създава се променлива от тип string
Инициализира се цикъл с променлива i = 0
, която се увеличава на всяка итерация на цикъла, докато е по-малка от 10. Така кодът в тялото на цикъла се изпълнява 10 пъти. В тялото на цикъла се добавя по една звездичка към низа. Това означава, че след края на цикъла низът ще съдържа 10 звездички. Инициализира се втори цикъл, който също се изпълнява 10 пъти. В тялото на втория цикъл се печата на нов ред в конзолата низа от 10 звездички, създаден преди това.
Как работи примерът? Инициализира се цикъл с променлива i
. Началната стойност по подразбиране на променливата е i = 0
. С всяка итерация на цикъла променливата се увеличава с 1, докато е по-малка от 10. Така кодът в тялото на цикъла се изпълнява 10 пъти - от 0-ия до 9-ия път включително. В тялото на цикъла се печата на нов ред в конзолата string(10, '*')
, което създава низ (стринг) от 10 звездички.
Тестване в Judge системата
Тествайте решението си тук: https://judge.softuni.org/Contests/Practice/Index/1366#0.
Пример: правоъгълник от N x N звездички
Да се напише програма, която въвежда цяло положително число n и печата на конзолата правоъгълник от N x N звездички.
Вход | Изход | Вход | Изход | Вход | Изход |
---|---|---|---|---|---|
2 | ** ** |
3 | *** *** *** |
4 | **** **** **** **** |
Насоки и подсказки
Задачата е аналогична с предходната:
Тестване в Judge системата
Тествайте решението си тук: https://judge.softuni.org/Contests/Practice/Index/1366#1.
Вложени цикли
Вложените цикли представляват конструкция, при която в тялото на един цикъл (външен) се изпълнява друг цикъл (вътрешен). За всяко завъртане на външния цикъл, вътрешният се извърта целият. Това се случва по следния начин:
- При стартиране на изпълнение на вложени цикли първо стартира външният цикъл: извършва се инициализация на неговата управляваща променлива и след проверка за край на цикъла, се изпълнява кодът в тялото му.
- След това се изпълнява вътрешният цикъл. Извършва се инициализация на началната стойност на управляващата му променлива, прави се проверка за край на цикъла и се изпълнява кодът в тялото му.
- При достигане на зададената стойност за край на вътрешния цикъл, програмата се връща една стъпка нагоре и се продължава започналото изпълнение на външния цикъл. Променя се с една стъпка управляващата променлива за външния цикъл, проверява се дали условието за край е удовлетворено и започва ново изпълнение на вложения (вътрешния) цикъл.
- Това се повтаря, докато променливата на външния цикъл достигне условието за край на цикъла.
Ето и един пример, с който нагледно да илюстрираме вложените цикли. Целта е да се отпечата отново правоъгълник от N x N звездички, като за всеки ред се извърта цикъл от 1 до N, а за всяка колона се извърта вложен цикъл от 1 до N:
int n;
cin >> n;
for (int r = 0; r < n; r++) {
for (int c = 0; c < n; c++) {
cout << "*";
}
cout << endl;
}
Може да тествате примера онлайн: https://repl.it/@vncpetrov/NestedLoopExample.
Да разгледаме примера по-горе. След инициализацията на първия (външен) цикъл, започва да се изпълнява неговото тяло, което съдържа втория (вложен) цикъл. Той сам по себе си печата на един ред n
на брой звездички. След като вътрешният цикъл приключи изпълнението си при първата итерация на външния, то след това външният ще продължи, т.е. ще отпечата един празен ред на конзолата. След това ще се извърши обновяване на променливата на първия цикъл и отново ще бъде изпълнен целият втори (вложен) цикъл. Вътрешният цикъл ще се изпълни толкова пъти, колкото се изпълнява тялото на външния цикъл, в случая n
пъти.
Пример: квадрат от звездички
Да се начертае на конзолата квадрат от N x N звездички:
Вход | Изход | Вход | Изход | Вход | Изход |
---|---|---|---|---|---|
2 | * * * * |
3 | * * * * * * * * * |
4 | * * * * * * * * * * * * * * * * |
Насоки и подсказки
Задачата е аналогична на предходната. Разликата тук е, че трябва да обмислим как да печатаме интервал след звездичките по такъв начин, че да няма излишни интервали в началото или края:
Тестване в Judge системата
Тествайте решението си тук: https://judge.softuni.org/Contests/Practice/Index/1366#2.
Пример: триъгълник от долари
Да се напише програма, която въвежда число n и печата триъгълник от долари.
Вход | Изход | Вход | Изход | Вход | Изход |
---|---|---|---|---|---|
3 | $ $ $ $ $ $ |
4 | $ $ $ $ $ $ $ $ $ $ |
5 | $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ |
Насоки и подсказки
Задачата е сходна с тези за рисуване на правоъгълник и квадрат. Отново ще използваме вложени цикли, но тук има уловка. Разликата е в това, че броя на колонките, които трябва да разпечатаме, зависят от реда, на който се намираме, а не от входното число n
. От примерните входни и изходни данни забелязваме, че броят на доларите зависи от това на кой ред се намираме към момента на печатането, т.е. 1 долар означава първи ред, 3 долара означават трети ред и т.н. Нека разгледаме долния пример по-подробно. Виждаме, че променливата на вложения цикъл е обвързана с променливата на външния. По този начин нашата програма печата желания триъгълник:
Тестване в Judge системата
Тествайте решението си тук: https://judge.softuni.org/Contests/Practice/Index/1366#3.
Пример: квадратна рамка
Да се напише програма, която въвежда цяло положително число n и чертае на конзолата квадратна рамка с размер N x N.
Вход | Изход | Вход | Изход |
---|---|---|---|
3 | + - + | - | + - + |
4 | + - - + | - - | | - - | + - - + |
Вход | Изход | Вход | Изход |
---|---|---|---|
5 | + - - - + | - - - | | - - - | | - - - | + - - - + |
6 | + - - - - + | - - - - | | - - - - | | - - - - | | - - - - | + - - - - + |
Насоки и подсказки
Можем да решим задачата по следния начин:
- Четем от конзолата числото
n
. - Отпечатваме горната част: първо знак
+
, после n - 2 пъти-
и накрая знак+
. - Отпечатваме средната част: печатаме n - 2 реда като първо печатаме знак
|
, после n - 2 пъти-
и накрая отново знак|
. Това можем да го постигнем с вложени цикли. - Отпечатваме долната част: първо
+
, после n - 2 пъти-
и накрая+
.
Ето и примерна имплементация на описаната идея, с вложени цикли:
Тестване в Judge системата
Тествайте решението си тук: https://judge.softuni.org/Contests/Practice/Index/1366#4.
Пример: ромбче от звездички
Да се напише програма, която въвежда цяло положително число n и печата ромбче от звездички с размер N.
Вход | Изход | Вход | Изход |
---|---|---|---|
1 | * |
2 | * * * * |
Вход | Изход | Вход | Изход |
---|---|---|---|
3 | * * * * * * * * * |
4 | * * * * * * * * * * * * * * * * |
Насоки и подсказки
За решението на тази задача е нужно да разделим мислено ромба на две части - горна, която включва и средния ред, и долна. За разпечатването на всяка една част ще използваме два отделни цикъла, като оставяме на читателя сам да намери зависимостта между n
и променливите на циклите. За първия цикъл може да използваме следните насоки:
- Отпечатваме
n - r - 1
интервала. - Отпечатваме
*
. - Отпечатваме
r
пъти*
.
Втората (долна) част ще изпечатим по аналогичен начин, което оставяме на читателя да се опита да направи сам.
Тестване в Judge системата
Тествайте решението си тук: https://judge.softuni.org/Contests/Practice/Index/1366#5.
Пример: коледна елха
Да се напише програма, която въвежда число n (1 ≤ n ≤ 100) и печата коледна елха с височина N + 1.
Вход | Изход | Вход | Изход |
---|---|---|---|
1 | | * | * |
2 | | * | * ** | ** |
Вход | Изход | Вход | Изход |
---|---|---|---|
3 | | * | * ** | ** *** | *** |
4 | | * | * ** | ** *** | *** **** | **** |
Насоки и подсказки
От примерите виждаме, че елхата може да бъде разделена на три логически части. Първата част включва звездичките и празните места преди и след тях, средната част е от |
, а последната част - отново звездички, като този път празни места има само преди тях:
Тестване в Judge системата
Тествайте решението си тук: https://judge.softuni.org/Contests/Practice/Index/1366#6.
Чертане на по-сложни фигури
Да разгледаме как можем да чертаем на конзолата фигури с по-сложна логика на конструиране, за които трябва повече да помислим, преди да почнем да пишем.
Пример: слънчеви очила
Да се напише програма, която въвежда цяло число n (3 ≤ n ≤ 100) и печата слънчеви очила с размер 5*N x N като в примерите:
Вход | Изход | Вход | Изход |
---|---|---|---|
3 | ****** ****** *////*|||*////* ****** ****** |
4 | ******** ******** *//////*||||*//////* *//////* *//////* ******** ******** |
Вход | Изход |
---|---|
5 | ********** ********** *////////* *////////* *////////*|||||*////////* *////////* *////////* ********** ********** |
Насоки и подсказки
От примерите виждаме, че очилата могат да се разделят на три части – горна, средна и долна. По-долу е част от кода, с който задачата може да се реши.
При рисуването на горния и долния ред трябва да се изпечатат 2 * n
звездички, n
интервала и 2 * n
звездички:
При печатането на средната част трябва да проверим дали редът е (n - 1) / 2 - 1
, тъй като от примерите е видно, че на този ред трябва да печатаме вертикални чертички вместо интервали:
Тестване в Judge системата
Тествайте решението си тук: https://judge.softuni.org/Contests/Practice/Index/1366#7.
Пример: къщичка
Да се напише програма, която въвежда число n (2 ≤ n ≤ 100) и печата къщичка с размери N x N, точно като в примерите.
Вход | Изход | Вход | Изход | Вход | Изход |
---|---|---|---|---|---|
2 | ** || |
3 | -*- *** |*| |
4 | -**- **** |**| |**| |
Вход | Изход | Вход | Изход |
---|---|---|---|
5 | --*-- -***- ***** |***| |***| |
8 | ---**--- --****-- -******- ******** |******| |******| |******| |******| |
Насоки и подсказки
Разбираме от условието на задачата, че къщата е с размер n
x n
. Това, което виждаме от примерните вход и изход, е, че:
- Къщичката е разделена на 2 части: покрив и основа.
- Когато
n
е четно число, върхът на къщичката е тъп. - Когато
n
е нечетно число, покривът е с остър връх и с един ред по-голям от основата.
Покрив
- Съставен е от звезди и тирета.
- В най-високата си част има една или две звезди, спрямо това дали n e четно или нечетно, както и тирета.
- В най-ниската си част има много звезди и малко или никакви тирета.
- С всеки един ред по-надолу звездите се увеличават с 2, а тиретата намаляват също с 2.
Основа
- Дълга е
n
на брой реда. - Съставена е от звезди и вертикални черти.
- Редовете са съставени от 2 вертикални черти - по една в началото и в края на реда, както и звезди между чертите с дължина на низа
n - 2
.
Решение
Прочитаме входното число n
от конзолата и записваме стойността му в променлива:
Много е важно да проверяваме дали са валидни входните данни! В тези задачи не е проблем директно да обръщаме прочетения вход от конзолата в тип int , защото изрично е казано, че ще получаваме валидни целочислени числа. Ако обаче правим по-сериозни приложения, е добра практика да проверяваме данните. Какво ще стане, ако вместо число потребителят въведе например буквата "А"? |
За да начертаем покрива, записваме колко ще е началният брой звезди в променлива stars
:
- Ако
n
е нечетно число, ще е 1 брой. - Ако
n
е четно, ще са 2 броя.
Изчисляваме дължината на покрива. Тя е равна на половината от n
. Резултата записваме в променливата roofLength
:
Забележка: За да използваме функцията ceil()
, която закръгля към по-голямото цяло число, без значение от дробната част, е необходимо да въведем библиотеката math.h
.
Важно е да съобразим, че когато n
е нечетно число, дължината на покрива е по-голяма с един ред от тази на основата. В езика C++, когато два целочислени типа се делят и има остатък, то резултатът ще е число без остатъка.
Пример:
int result = 3 / 2; // Result is 1
Може да тествате примера онлайн: https://repl.it/@vncpetrov/DivisionExample.
Ако искаме да закръглим нагоре, трябва да използваме функцията ceil(…)
:
#include <iostream>
#include <math.h>
using namespace std;
int main() {
int result1 = ceil(3 / 2.0); // резултат 2
int result2 = ceil(7 / 2.7); // резултат 3
}
Може да тествате примера онлайн: https://repl.it/@vncpetrov/ExmapleWithCeil.
В този пример делението не е от 2 целочислени числа. Когато цяло число е записано със суфикс '.0'
, това показва, че даденото число е от тип число с плаваща запетая. Резултатът от 3 / 2.0
е 1.5
, а функцията ceil(…)
закръгля резултата от делението нагоре. В нашият случай 1.5
ще стане 2.0
, което се превръща в целочисленото 2
.
След като сме изчислили дължината на покрива, завъртаме цикъл от 0 до roofLength
. На всяка итерация ще:
- Изчисляваме броя тирета, които трябва да изрисуваме. Броят ще е равен на
(n - stars) / 2
. Записваме го в променливаpadding
:
- Отпечатваме на конзолата: "тирета" (
padding
на брой пъти) + "звезди" (stars
пъти) + "тирета" (padding
пъти):
- Преди да завърши въртенето на цикъла увеличаваме
stars
(броя на звездите) с 2:
След като сме приключили с покрива, е време за основата. Тя е по-лесна за печатане:
- Започваме с цикъл от 0 до
n
(изключено). - Отпечатваме на конзолата:
|
+*
(n - 2
на брой пъти) +|
.
Ако всичко сме написали както трябва, задачата ни е решена.
Тестване в Judge системата
Тествайте решението си тук: https://judge.softuni.org/Contests/Practice/Index/1366#8.
Пример: диамант
Да се напише програма, която приема цяло число n (1 ≤ n ≤ 100) и печата диамант с размер N, като в следните примери:
Вход | Изход | Вход | Изход | Вход | Изход |
---|---|---|---|---|---|
1 | * |
2 | ** |
3 | -*- *-* -*- |
Вход | Изход | Вход | Изход | Вход | Изход |
---|---|---|---|---|---|
4 | -**- *--* -**- |
5 | --*-- -*-*- *---* -*-*- --*-- |
6 | --**-- -*--*- *----* -*--*- --**-- |
Вход | Изход | Вход | Изход | Вход | Изход |
---|---|---|---|---|---|
7 | ---*--- --*-*-- -*---*- *-----* -*---*- --*-*-- ---*--- |
8 | ---**--- --*--*-- -*----*- *------* -*----*- --*--*-- ---**--- |
9 | ----*---- ---*-*--- --*---*-- -*-----*- *-------* -*-----*- --*---*-- ---*-*--- ----*---- |
Насоки и подсказки
Това, което знаем от условието на задачата, е че диамантът е с размер n
x n
. От примерните вход и изход можем да си направим извода, че всички редове съдържат точно по n
символа и всички редове, с изключение на горните върхове, имат по 2 звезди. Можем мислено да разделим диаманта на 2 части:
- Горна част. Тя започва от горния връх до средата.
- Долна част. Тя започва от реда след средата до най-долния връх включително.
Горна част
- Ако
n
е нечетно, то тя започва с 1 звезда. - Ако
n
е четно, то тя започва с 2 звезди. - С всеки ред надолу звездите се отдалечават една от друга.
- Пространството между, преди и след звездите е запълнено с тирета.
Долна част
- С всеки ред надолу звездите се приближават една към друга. Това означава, че пространството (тиретата) между тях намалява, а пространството (тиретата) отляво и отдясно се увеличава.
- В най-долната си част е с 1 или 2 звезди, спрямо това дали
n
е четно или не.
Горна и долна част на диаманта
- На всеки ред звездите са заобиколени от външни тирета, с изключение на средния ред.
- На всеки ред има пространство между двете звезди, с изключение на първия и последния ред (понякога звездата е 1).
Създаваме променлива n
от тип int
и прочитаме стойността ѝ от конзолата:
Започваме да печатаме на конзолата горната част на диаманта. Първото нещо, което трябва да направим, е да изчислим началната стойност на външната бройка тирета leftRight
(тиретата от външната част на звездите). Тя е равна на (n - 1) / 2
, закръглено надолу:
След като сме изчислили leftRight
, започваме да чертаем горната част на диаманта. Може да започнем, като завъртим цикъл от 0
до (n + 1) // 2
(т.е. закръглено надолу).
При всяка итерация на цикъла трябва да се изпълнят следните стъпки:
- Рисуваме по конзолата левите тирета (с дължина
leftRight
) и веднага след тях първата звезда.
- Изчисляваме разстоянието между двете звезди. Може да го направим като извадим от
n
дължината на външните тирета, както и числото 2 (бройката на звездите, т.е. очертанията на диаманта). Резултата от тази разлика записваме в променливаmid
.
Ако стойността на
mid
е по-малка от 0, то тогава знаем, че на този ред трябва да има 1 звезда. Ако е по-голяма или равно на 0, то тогава трябва да начертаем тирета с дължинаmid
и една звезда след тях.Печатаме на конзолата десните външни тирета с дължина също
leftRight
.
- В края на цикъла намаляваме
leftRight
с 1 (звездите се отдалечават).
Готови сме с горната част.
Чертането на долната част е доста подобно на това на горната част. Разликата е, че вместо да намаляме стойността на leftRight
с 1 към края на цикъла, ще я увеличаваме с 1 в началото на цикъла. Също така цикълът ще се върти от 0 до (n - 1) // 2
:
Повторението на код се смята за лоша практика, защото кодът става доста труден за поддръжка. Нека си представим, че имаме парче код (напр. логиката за чертането на ред от диаманта) на още няколко места и решаваме да направим промяна. За целта би било необходимо да минем през всичките места и да направим промените. А сега нека си представим, че трябва да използвате код не 1, 2 или 3 пъти, а десетки пъти. Начин за справяне с този проблем е като се използват функции. Можете да потърсите допълнителна информация за тях в Интернет или да прегледате Глава 10. Функции. |
Ако сме написали всичко коректно, задачата ни е решена.
Тестване в Judge системата
Тествайте решението си тук: https://judge.softuni.org/Contests/Practice/Index/1366#9.
Какво научихме от тази глава?
Запознахме се с един от начините за създаване на низове:
string printMe = string(10, '*');
Може да тествате примера онлайн: https://repl.it/@vncpetrov/PrintMe.
Научихме се да чертаем фигури с вложени for
цикли:
for (int r = 0; r < 5; r++) {
cout << "*";
for (int c = 0; c < 5 - 1; c++) {
cout << " *";
}
cout << endl;
}
Може да тествате примера онлайн: https://repl.it/@vncpetrov/ForLoopFigure.