Глава 7.1. По-сложни цикли
След като научихме какво представляват и за какво служат for
циклите, сега предстои да се запознаем с други видове цикли, както и с някои по-сложни конструкции за цикъл. Те ще разширят познанията ни и ще ни помагат в решаването на по-трудни и по-предизвикателни задачи. По-конкретно, ще разгледаме как се ползват следните програмни конструкции:
- цикли със стъпка
while
циклиwhile
+break
цикли- безкрайни цикли
В настоящата тема ще разберем и какво представлява операторът break
, както и как чрез него да прекъснем един цикъл. Ще се научим още как да следим за грешки по време на изпълнението на програмата, използвайки try-except
конструкцията.
Видео
Цикли със стъпка
В главата "Повторения (цикли)" научихме как работи for
цикълът и вече знаем кога и с каква цел да го използваме. В тази тема ще обърнем внимание на една определена и много важна част от конструкцията му, а именно стъпката.
Какво представлява стъпката?
Стъпката е тази част от конструкцията на range
функцията, която указва с колко да се увеличи или намали стойността на водещата му променлива. Тя се декларира последна в скелета на range
функцията.
По подразбиране е с размер 1
и не се добавя в range
функцията. Ако искаме стъпката ни да е различна от 1, при писане на range
функцията, добавяме още едно число, което е нашата стъпка. При стъпка 10
, цикълът би изглеждал по следния начин:
Следва поредица от примерни задачи, решението на които ще ни помогне да разберем по-добре употребата на стъпката във for
цикъл.
Пример: числата от 1 до N през 3
Да се напише програма, която отпечатва числата от 1 до n със стъпка 3. Например, ако n = 100, то резултатът ще е: 1, 4, 7, 10, …, 94, 97, 100.
Можем да решим задачата чрез следната поредица от действия (алгоритъм):
- Четем числото
n
от входа на конзолата. - Изпълняваме
for
цикъл от 1 доn
(включително иn
) с размер на стъпката 3. - В тялото на цикъла отпечатваме стойността на текущата стъпка.
Тестване в Judge системата
Тествайте решението си тук: https://judge.softuni.org/Contests/Practice/Index/1057#0.
Пример: числата от N до 1 в обратен ред
Да се напише програма, която отпечатва числата от n до 1 в обратен ред (стъпка -1). Например, ако n = 100, то резултатът ще е: 100, 99, 98, …, 3, 2, 1.
Можем да решим задачата по следния начин:
- Четем числото
n
от входа на конзолата. - Създаваме
for
цикъл, отn
до 0. - Дефинираме размера на стъпката: -1.
- В тялото на цикъла отпечатваме стойността на текущата стъпка.
Тестване в Judge системата
Тествайте решението си тук: https://judge.softuni.org/Contests/Practice/Index/1057#1.
Пример: числата от 1 до 2^n с for цикъл
В следващия пример ще разгледаме ползването на обичайната стъпка с размер 1.
Да се напише програма, която отпечатва числата от 1 до 2^n (две на степен n). Например, ако n = 10, то резултатът ще е 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024.
Тестване в Judge системата
Тествайте решението си тук: https://judge.softuni.org/Contests/Practice/Index/1057#2.
Пример: четни степени на 2
Да се отпечатат четните степени на 2 до 2^n: 2^0, 2^2, 2^4, 2^8, …, 2^n. Например, ако n = 10, то резултатът ще е 1, 4, 16, 64, 256, 1024.
Ето как можем да решим задачата:
- Създаваме променлива
num
за текущото число, на която присвояваме начална стойност 1. - За стъпка на цикъла задаваме стойност 2.
- В тялото на цикъла: отпечатваме стойността на текущото число и увеличаваме текущото число
num
4 пъти (според условието на задачата).
Тестване в Judge системата
Тествайте решението си тук: https://judge.softuni.org/Contests/Practice/Index/1057#3.
While цикъл
Следващият вид цикли, с които ще се запознаем, се наричат while
цикли. Специфичното при тях е, че повтарят блок от команди, докато дадено условие е истина. Като структура се различават от тази на for
циклите, даже имат опростен синтаксис.
Какво представлява while цикълът?
В програмирането while
цикълът се използва, когато искаме да повтаряме извършването на определена логика, докато е в сила дадено условие. Под "условие", разбираме всеки израз, който връща true
или false
. Когато условието стане грешно, while
цикълът прекъсва изпълнението си и програмата продължава с изпълняването на кода след цикъла. Конструкцията за while
цикъл изглежда по този начин:
Следва поредица от примерни задачи, решението на които ще ни помогне да разберем по-добре употребата на while
цикъла.
Пример: редица числа 2k+1
Да се напише програма, която отпечатва всички числа ≤ n от редицата: 1, 3, 7, 15, 31, …, като приемем, че всяко следващо число = предишно число * 2 + 1.
Ето как можем да решим задачата:
- Създаваме променлива
num
за текущото число, на която присвояваме начална стойност 1. - За условие на цикъла слагаме текущото число <= n.
- В тялото на цикъла: отпечатваме стойността на текущото число и увеличаваме текущото число, използвайки формулата от условието на задачата.
Ето и примерна реализация на описаната идея:
Тестване в Judge системата
Тествайте решението си тук: https://judge.softuni.org/Contests/Practice/Index/1057#4.
Пример: число в диапазона [1 … 100]
Да се въведе цяло число в диапазона [1 … 100]. Ако то е невалидно, да се въведе отново. В случая, за невалидно число ще считаме всяко такова, което не е в зададения диапазон.
За да решим задачата, можем да използваме следния алгоритъм:
- Създаваме променлива
num
, на която присвояваме целочислената стойност, получена от входа на конзолата. - За условие на цикъла слагаме израз, който е
True
, ако числото от входа не е в диапазона посочен в условието. - В тялото на цикъла: отпечатваме съобщение със съдържание "Invalid number!" на конзолата, след което присвояваме нова стойност за
num
от входа на конзолата. - След като вече сме валидирали въведеното число, извън тялото на цикъла отпечатваме стойността му.
Ето и примерна реализация на алгоритъма чрез while
цикъл:
Тестване в Judge системата
Тествайте решението си тук: https://judge.softuni.org/Contests/Practice/Index/1057#5.
Най-голям общ делител (НОД)
Преди да продължим към следващата задача, е необходимо да се запознаем с определението за най-голям общ делител (НОД).
Определение за НОД: най-голям общ делител на две естествени числа a и b е най-голямото число, което се дели едновременно и на a, и на b без остатък. Например:
a | b | НОД |
---|---|---|
24 | 16 | 8 |
67 | 18 | 1 |
12 | 24 | 12 |
15 | 9 | 3 |
10 | 10 | 10 |
100 | 88 | 4 |
Алгоритъм на Евклид
В следващата задача ще използваме един от първите публикувани алгоритми за намиране на НОД - алгоритъм на Евклид:
Докато не достигнем остатък 0:
- Делим по-голямото число на по-малкото.
- Вземаме остатъка от делението.
Псевдо-код за алгоритъма на Евклид:
while b ≠ 0
oldB = b
b = a % b
a = oldB
print а
Пример: най-голям общ делител (НОД)
Да се подадат цели числа a и b и да се намери НОД(a, b).
Ще решим задачата чрез алгоритъма на Евклид:
- Създаваме променливи
a
иb
, на които присвояваме целочислени стойности, взети от входа на конзолата. - За условие на цикъла слагаме израз, който е
True
, ако числотоb
е различно от 0. - В тялото на цикъла следваме указанията от псевдо кода:
- Създаваме временна променлива, на която присвояваме текущата стойност на
b
. - Присвояваме нова стойност на
b
, която е остатъка от делението наa
иb
. - На променливата
a
присвояваме предишната стойност на променливатаb
.
- Създаваме временна променлива, на която присвояваме текущата стойност на
- След като цикълът приключи и сме установили НОД, го отпечатваме на екрана.
Тестване в Judge системата
Тествайте решението си тук: https://judge.softuni.org/Contests/Practice/Index/1057#6.
While True + break цикъл
Следващият тип конструкция за цикъл, с която ще се запознаем докато изучаваме програмирането, е while True
+ break
цикълът (за по-кратко while
+ break
цикъл). Неговата идея е да повтаря фрагмент от кода многократно докато не се достигне до изрично прекратяване на цикъла, обикновено след if
проверка в тялото на цикъла. Ето как изглежда този цикъл на практика като Python код:
Горният пример се нарича “обърнат while
цикъл”, защото условието за изход от цикъла е накрая, а не в началото. По същина горната конструкция представлява “безкраен цикъл” с проверка на дадено условие за изход вътре в тялото на цикъла.
В програмирането операторът break
безусловно прекратява даден цикъл и преминава към първата инструкция веднага след него. В горния пример в края на цикъла се проверява дадено условие и ако то не е вярно, цикълът се прекратява.
Конструкцията за цикъл while
+ brea
k в много други езици за програмиране се реализира с do-while
конструкция (в превод “прави-докато”), но последната няма директен еквивалент в Python. За да се постигне същото поведение, в Python се използва безкраен цикъл (while True
) и когато се достигне условието за изход от него, цикълът се прекъсва (с break
).
Конструкцията while
+ break
предоставя повече гъвкавост, отколкото while
циклите, защото позволява изходът от цикъла да е на произволно място в него (например в началото, в средата или в края), дори позволява да има изход от цикъла на няколко различни места (с няколко break
оператора).
По структура while
+ break
цикълът наподобява много класическия while
цикъл, но има съществена разлика: while
се изпълнява 0 или повече пъти (според входното условие на цикъла), докато while
+ break
изпълнява тялото си поне веднъж. Защо се случва това? В конструкцията на while True
+ break
цикъла, условието винаги се проверява вътре в тялото му, докато при класическият while
цикъл проверката за изход от цикъла е винаги в началото, преди неговото тяло.
След като се запознахме с концепцията за while
+ break
цикъл с условие за изход, което не е задължително в началото, сега нека преминем през обичайната поредица от примерни задачи, в които можем да приложим наученото.
Пример: изчисляване на факториел
За естествено число n да се изчисли n! = 1 * 2 * 3 * … * n. Например, ако n = 5, то резултатът ще бъде: 5! = 1 * 2 * 3 * 4 * 5 = 120.
Ето как по-конкретно можем да пресметнем факториел:
- Създаваме променливата
n
, на която присвояваме целочислена стойност взета от входа на конзолата. - Създаваме още една променлива -
fact
, чиято начална стойност е 1. Нея ще използваме за изчислението и съхранението на факториела. - За условие на цикъла ще използваме
n > 1
, тъй като всеки път, когато извършим изчисленията в тялото на цикъла, ще намаляваме стойността наn
с 1. - В тялото на цикъла:
- Присвояваме нова стойност на
fact
, която е резултат от умножението на текущата стойност наfact
с текущата стойност наn
. - Намаляваме стойността на
n
с -1.
- Присвояваме нова стойност на
- Извън тялото на цикъла отпечатваме крайната стойност на факториела.
Тестване в Judge системата
Тествайте решението си тук: https://judge.softuni.org/Contests/Practice/Index/1057#7.
Пример: сумиране на цифрите на число
Да се сумират цифрите на цяло положително число n. Например, ако n = 5634, то резултатът ще бъде: 5 + 6 + 3 + 4 = 18.
Можем да използваме следната идея, за да решим задачата:
- Създаваме променливата
n
, на която присвояваме стойност, равна на въведеното от потребителя число. - Създаваме втора променлива -
sum
, чиято начална стойност е 0. Нея ще използваме за изчислението и съхранението на резултата. - За условие на цикъла ще използваме
n > 0
, тъй като след всяко изчисление на резултата в тялото на цикъла, ще премахваме последната цифра отn
. - В тялото на цикъла:
- Присвояваме нова стойност на
sum
, която е резултат от събирането на текущата стойност наsum
с последната цифра наn
. - Присвояваме нова стойност на
n
, която е резултат от премахването на последната цифра отn
.
- Присвояваме нова стойност на
- Извън тялото на цикъла отпечатваме крайната стойност на сумата.
n % 10 : връща последната цифра на числото n .n // 10 : изтрива последната цифра на n . |
Тестване в Judge системата
Тествайте решението си тук: https://judge.softuni.org/Contests/Practice/Index/1057#8.
Безкрайни цикли и операторът break
До момента се запознахме с различни видове цикли, като научихме какви конструкции имат те и как се прилагат. Следва да разберем какво е безкраен цикъл, кога възниква и как можем да прекъснем изпълнението му чрез оператора break
.
Безкраен цикъл. Що е то?
Безкраен цикъл наричаме този цикъл, който повтаря безкрайно изпълнението на тялото си. При безкрайните while
цикли проверката за край е условен израз, който винаги връща true
. Ето как изглежда безкраен while
цикъл:
Оператор break
Вече знаем, че безкрайният цикъл изпълнява определен код до безкрайност, но какво става, ако желаем в определен момент при дадено условие, да излезем принудително от цикъла? На помощ идва операторът break
, в превод - спри, прекъсни.
Операторът break спира изпълнението на цикъла към момента, в който е извикан, и продължава от първия ред след края на цикъла. Това означава, че текущата итерация на цикъла няма да бъде завършена до край и съответно останалата част от кода в тялото на цикъла няма да се изпълни. |
Пример: прости числа
В следващата задача се изисква да направим проверка за просто число. Преди да продължим към нея, нека си припомним какво са простите числа.
Определение: едно цяло число е просто, ако се дели без остатък единствено на себе си и на 1. По дефиниция простите числа са положителни и по-големи от 1. Най-малкото просто число е 2.
Можем да приемем, че едно цяло число n е просто, ако n > 1 и n не се дели на число между 2 и n-1.
Първите няколко прости числа са: 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, …
За разлика от тях, непростите (композитни) числа са такива числа, чиято композиция е съставена от произведение на прости числа.
Ето няколко примерни непрости числа:
- 10 = 2 * 5
- 42 = 2 * 3 * 7
- 143 = 13 * 11
Алгоритъм за проверка дали дадено цяло число е просто: проверяваме дали n > 1 и дали n се дели на 2, 3, …, n-1 без остатък.
- Ако се раздели на някое от числата, значи е композитно.
- Ако не се раздели на никое от числата, значи е просто.
Можем да оптимизираме алгоритъма, като вместо проверката да е до n-1 , да се проверяват делителите до √n . Помислете защо. |
Пример: проверка за просто число. Оператор break
Да се провери дали едно число n е просто. Това ще направим като проверим дали n се дели на числата между 2 и √n.
Ето го алгоритъма за проверка за просто число, разписан постъпково:
- Създаваме променливата
n
, на която присвояваме цяло число въведено от входа на конзолата. - Създаваме булева променлива
is_prime
с начална стойностTrue
. Приемаме, че едно число е просто до доказване на противното. - Създаваме
for
цикъл, на който като начална стойност за променливата на цикъла задаваме 2, за условие текущата ѝ стойност<= √n
. Стъпката на цикъла е 1. - В тялото на цикъла проверяваме дали
n
, разделено на текущата стойност има остатък. Ако от делението няма остатък, то променямеis_prime
наFalse
и излизаме принудително от цикъла чрез операторbreak
. - В зависимост от стойността на
is_prime
отпечатваме дали числото е просто (True
) или съответно съставно (False
).
Ето и примерна имплементация на описания алгоритъм:
Оставаме да добавите проверка дали входното число е по-голямо от 1, защото по дефиниция числа като 0, 1, -1 и -2 не са прости.
Тестване в Judge системата
Тествайте решението си тук: https://judge.softuni.org/Contests/Practice/Index/1057#9.
Пример: оператор break в безкраен цикъл
Да се напише програма, която проверява дали едно число n е четно, ако е - да се отпечатва на екрана. За четно считаме число, което се дели на 2 без остатък. При невалидно число да се връща към повторно въвеждане и да се изписва съобщение, което известява, че въведеното число не е четно.
Ето една идея как можем да решим задачата:
- Създаваме променлива
n
, на която присвояваме начална стойност 0. - Създаваме безкраен
while
цикъл, като за условие ще зададемTrue
. - В тялото на цикъла:
- Вземаме целочислена стойност от входа на конзолата и я присвояваме на
n
. - Ако числото е четно, излизаме от цикъла чрез
break
. - В противен случай извеждаме съобщение, което гласи, че числото не е четно. Итерациите продължават, докато не се въведе четно число.
- Вземаме целочислена стойност от входа на конзолата и я присвояваме на
- Отпечатваме четното число на екрана.
Ето и примерна имплементация на идеята:
Забележка: макар кодът по-горе да е коректен, той няма да работи, ако вместо число потребителят въведе текст, например "Invalid number". Тогава парсването на текста към число ще се счупи и програмата ще покаже съобщение за грешка (изключение). Как да се справим с този проблем и как да прихващаме и обработваме изключения чрез try-except
конструкцията ще научим след малко.
Тестване в Judge системата
Тествайте решението си тук: https://judge.softuni.org/Contests/Practice/Index/1057#10.
Вложени цикли и операторът break
След като вече научихме какво са вложените цикли и как работи операторът break
, е време да разберем как работят двете заедно. За по-добро разбиране, нека стъпка по стъпка да напишем програма, която трябва да направи всички възможни комбинации от двойки числа. Първото число от комбинацията е нарастващо от 1 до 3, а второто е намаляващо от 3 до 1. Задачата трябва да продължи изпълнението си, докато i + j
не е равно на 2 (т.е. i = 1
и j = 1
).
Желаният резултат е:
Ето едно грешно решение, което изглежда правилно на пръв поглед:
Ако оставим програмата ни по този начин, резултатът ни ще е следният:
Защо се получава така? Както виждаме, в резултата липсва "1 1". Когато програмата стига до там, че i = 1
и j = 1
, тя влиза в if
проверката и изпълнява break
операцията. По този начин се излиза от вътрешния цикъл, но след това продължава изпълнението на външния. i
нараства, програмата влиза във вътрешния цикъл и принтира резултата.
Когато във вложен цикъл използваме оператора break , той прекъсва изпълнението само на вътрешния цикъл. |
Какво е правилното решение? Един начин за решаването на този проблем е чрез деклариране на bool
променлива, която следи за това, дали трябва да продължава въртенето на цикъла. При нужда от изход (излизане от всички вложени цикли), се променя стойността на променливата на True
и се излиза от вътрешния цикъл с break
, а при последваща проверка се напуска и външния цикъл. Ето и примерна имплементация на тази идея:
По този начин, когато i + j = 2
, програмата ще направи променливата has_to_end = True
и ще излезе от вътрешния цикъл. При следващото завъртане на външния цикъл, чрез if
проверката, програмата няма да може да стигне до вътрешния цикъл и ще прекъсне изпълнението си.
Тестване в Judge системата
Тествайте решението си тук: https://judge.softuni.org/Contests/Practice/Index/1057#11.
Справяне с изключения: try-except
Последното, с което ще се запознаем в тази глава, е как да "улавяме" грешки (exceptions) чрез конструкцията try-except
.
Какво е try-except?
Програмната конструкция try-except
служи за прихващане и обработка на изключения (грешки) по време на изпълнението на програмата.
В програмирането изключенията (exceptions) представляват уведомление за дадено събитие, което нарушава нормалната работа на една програма. Такива събития прекъсват изпълнението на програмата и тя търси кой да обработи настъпилата ситуация. Ако не намери, изключението се отпечатва на конзолата (т.е. програмата "гърми"). Ако намери, изключението се обработва и програмата продължава нормалното си изпълнение, без да "гърми". След малко ще видим как точно става това.
Конструкция на try-except
Конструкцията try-except
има различни варианти, но за сега ще се запознаем само с най-основния от тях:
В следващата задача ще видим нагледно, как да се справим в ситуация, в която потребителят въвежда вход, различен от число (например string
вместо int
), чрез try-except
.
Пример: справяне с невалидни числа чрез try-except
Да се напише програма, която проверява дали едно число n е четно и ако е, да се отпечата на екрана. При невалидно въведено число да се изписва съобщение, че въведения вход не е валидно число и въвеждането да продължи отново.
Ето как можем да решим задачата:
- Създаваме безкраен
while
цикъл, като за условие ще зададемTrue
. - В тялото на цикъла:
- Създаваме
try-except
конструкция. - В
try
блока пишем програмната логика за четене на потребителския вход, парсването му до число и проверката за четност. - При четно число го отпечатваме и излизаме от цикъла (с
break
). Програмата си е свършила работата и приключва. - При нечетно число отпечатваме съобщение, че се изисква четно число, без да излизаме от цикъла (защото искаме той да се повтори отново).
- Ако хванем изключение при изпълнението на
try
блока, изписваме съобщение за невалидно въведено число (и цикълът съответно се повтаря, защото не излизаме изрично от него).
- Създаваме
Ето и примерна имплементация на описаната идея:
Тестване в Judge системата
Тествайте решението си тук: https://judge.softuni.org/Contests/Practice/Index/1057#10.
Сега вече решението трябва да работи винаги: независимо дали въвеждаме цели числа, невалидни числа (например твърде много цифри) или текст, които не съдържат числа.
Задачи с цикли
В тази глава се запознахме с няколко нови вида цикли, с които могат да се правят повторения с по-сложна програмна логика. Да решим няколко задачи, използвайки новите знания.
Задача: числа на Фибоначи
Числата на Фибоначи в математиката образуват редица, която изглежда по следния начин: 1, 1, 2, 3, 5, 8, 13, 21, 34, ….
Формулата за образуване на редицата е:
F0 = 1
F1 = 1
Fn = Fn-1 + Fn-2
Примерен вход и изход
Вход (n) | Изход | Коментар |
---|---|---|
10 | 89 | F(11) = F(9) + F(8) |
5 | 8 | F(5) = F(4) + F(3) |
20 | 10946 | F(20) = F(19) + F(18) |
0 | 1 | |
1 | 1 |
Да се въведе цяло число n и да се пресметне n-тото число на Фибоначи.
Насоки и подсказки
Идея за решаване на задачата:
- Създаваме променлива
n
, на която присвояваме целочислена стойност от входа на конзолата. - Създаваме променливите
f0
иf1
, на които присвояваме стойност 1, тъй като така започва редицата. - Създаваме
for
цикъл от нула до крайна стойностn - 1
. - В тялото на цикъла:
- Създаваме временна променлива
f_next
, на която присвояваме следващото число в поредицата на Фибоначи. - На
f0
присвояваме текущата стойност наf1
. - На
f1
присвояваме стойността на временната променливаf_next
.
- Създаваме временна променлива
- Извън цикъла отпечатваме числото n-тото число на Фибоначи.
Примерна имплементация:
Тестване в Judge системата
Тествайте решението си тук: https://judge.softuni.org/Contests/Practice/Index/1057#12.
Задача: пирамида от числа
Да се отпечатат числата 1 … n в пирамида като в примерите по долу. На първия ред печатаме едно число, на втория ред печатаме две числа, на третия ред печатаме три числа и т.н. докато числата свършат. На последния ред печатаме толкова числа, колкото останат докато стигнем до n.
Примерен вход и изход
Вход | Изход | Вход | Изход | Вход | Изход |
---|---|---|---|---|---|
7 | 1 2 3 4 5 6 7 |
5 | 1 2 3 4 5 |
10 | 1 2 3 4 5 6 7 8 9 10 |
Насоки и подсказки
Можем да решим задачата с два вложени цикъла (по редове и колони) с печатане в тях и излизане при достигане на последното число. Ето идеята, разписана по-подробно:
- Създаваме променлива
n
, на която присвояваме целочислена стойност, прочетена от конзолата. - Създаваме променлива
num
с начална стойност 1. Тя ще пази броя на отпечатаните числа. При всяка итерация ще я увеличаваме с 1 и ще я принтираме. - Създаваме външен
for
цикъл, който ще отговаря за редовете в таблицата. Наименуваме променливата на цикълаrow
и ѝ задаваме начална стойност 1. За крайна стойност слагамеn + 1
. - В тялото на цикъла създаваме вътрешен
for
цикъл, който ще отговаря за колоните в таблицата. Наименуваме променливата на цикълаcol
и ѝ задаваме начална стойност 1. За условие слагамеrow + 1
(row
= брой цифри на ред). - В тялото на вложения цикъл:
- Проверяваме дали
col > 1
, ако да – принтираме разстояние. Ако не направим тази проверка, а директно принтираме разстоянието, ще имаме ненужно такова в началото на всеки ред. - Отпечатваме числото
num
в текущата клетка на таблицата и го увеличаваме с 1. - Правим проверка за
num > n
. Акоnum
е по-голямо отn
, прекъсваме въртенето на вътрешния цикъл.
- Проверяваме дали
- Отпечатваме празен ред, за да преминем на следващия.
- Отново проверяваме дали
num > n
. Ако е по-голямо, прекъсваме изпълнението на програмата ни чрезbreak
.
Ето и примерна имплементация:
Тестване в Judge системата
Тествайте решението си тук: https://judge.softuni.org/Contests/Practice/Index/1057#13.
Задача: таблица с числа
Да се отпечатат числата 1 … n в таблица като в примерите по-долу.
Примерен вход и изход
Вход | Изход | Вход | Изход |
---|---|---|---|
3 | 1 2 3 2 3 2 3 2 1 |
4 | 1 2 3 4 2 3 4 3 3 4 3 2 4 3 2 1 |
Насоки и подсказки
Можем да решим задачата с два вложени цикъла и малко изчисления в тях:
- Четем от конзолата размера на таблицата в целочислена променлива
n
. - Създаваме
for
цикъл, който ще отговаря за редовете в таблицата. Наименуваме променливата на цикълаrow
и ѝ задаваме начална стойност 0. За крайна стойност слагамеn
. Размерът на стъпката е 1. - В тялото на цикъла създаваме вложен
for
цикъл, който ще отговаря за колоните в таблицата. Наименуваме променливата на цикълаcol
и ѝ задаваме начална стойност 0. За крайна стойност слагамеn
. Размерът на стъпката е 1. - В тялото на вложения цикъл:
- Създаваме променлива
num
, на която присвояваме резултата от текущият ред + текущата колона + 1 (+1, тъй като започваме броенето от 0). - Правим проверка за
num > n
. Акоnum
е по-голямо отn
, присвояваме нова стойност наnum
равна на два пътиn
- текущата стойност заnum
. Това правим с цел да не превишавамеn
** в никоя от клетките на таблицата.- Отпечатваме числото от текущата клетка на таблицата.
- Създаваме променлива
- Отпечатваме празен ред във външния цикъл, за да преминем на следващия ред.
Ето и примерна имплементация на описаната идея:
Тестване в Judge системата
Тествайте решението си тук: https://judge.softuni.org/Contests/Practice/Index/1057#14.
Какво научихме от тази глава?
Можем да използваме for
цикли със стъпка:
for i in range(1, n + 1, 3):
print(i)
Циклите while
се повтарят докато е в сила дадено условие:
num = 1
while num <= n:
print(num)
num += 1
Ако се наложи да прекъснем изпълнението на цикъл, го правим с оператора break
:
n = 0
while True:
n = int(input())
if n % 2 == 0:
break # even number -> exit from the loop
print("The number is not even.")
print("Even number entered: {}".format(n))
Вече знаем как да прихващаме грешки по време на изпълнението на програмата ни:
try:
print("Enter even number: ", end="")
n = int(input())
except ValueError:
print("Invalid number.")
# Ако int(…) гръмне, ще се изпълни except блокът
Упражнения: уеб приложения с по-сложни цикли
Сега вече знаем как да повтаряме група действия, използвайки цикли. Нека направим нещо интересно: уеб базирана игра. Да, истинска игра, с графика, с гейм логика. Да се позабавляваме. Ще бъде сложно, но ако не разберете нещо как точно работи, няма проблем. Сега още навлизаме в програмирането. Има време, ще напреднете с технологиите. Засега следвайте стъпките.
Задача: уеб игра "Обстреляй плодовете!"
Условие: Да се разработи Flask уеб приложение – игра, в която играчът стреля по плодове, подредени в таблица. Успешно уцелените плодове изчезват, а играчът получава точки за всеки уцелен плод. При уцелване на динамит, плодовете се взривяват и играта свършва (както в игри като Fruit Ninja). Стрелбата се извършва по колони, отгоре надолу или отдолу нагоре, а местоположението на удара (колоната под обстрел) се задава чрез скролер (scroll bar). Заради неточността на скролера, играчът не е съвсем сигурен по коя колона ще стреля. Така при всеки изстрел има шанс да не улучи и това прави играта по-интересна (подобно на прашката в Angry Birds).
Играта ни трябва да изглежда по този начин:
Следват стъпките за имплементация на уеб приложението "Обстреляй плодовете!".
Празно PyCharm решение
Създаваме празно решение в PyCharm, за да организираме кода от приложението:
След това, задаваме смислено име на проекта, например "Fruits-Web-Game". Също така, задаваме Python интерпретатора на този по подразбиране:
Ще създадем нашето уеб приложение, ползвайки библиотеката Flask, с която вече се запознахме. Както вече знаем, преди да започнем да пишем код, трябва да я инсталираме. Отиваме в настройките на PyCharm [File] -> [Settings] и след това в [Project: Fruit-Web-Game] -> [Project Interpreter]. Там, натискаме бутона +
, търсим и инсталираме Flask
.
Сега трябва да вземем структурата на проекта от предоставените ни ресурси.
След това, добавяме ресурсите за играта (те са част от файловете със заданието за този проект и могат да бъдат свалени от тук). Копираме ги от Windows Explorer и ги поставяме в папката на проекта в PyCharm с copy/paste. След като поставим ресурсите, структурата на проекта трябва да изглежда така:
Ако отворим app.py и го пуснем с десен бутон -> [Run 'app'], би трябвало да се отвори приложението, след което да натиснем върху линка в конзолата и да се отвори нашия уеб браузър:
Сега създаваме контролите за играта. Целта е да добавим скролиращи ленти (scroll bars), с които играчът да се прицелва, и бутон за стартиране на нова игра. Затова трябва да редактираме файла templates/index.html. Изтриваме "Hello World" и на негово място въвеждаме следния код:
Този код създава уеб форма <form>
със скролер (поле) position
за задаване на число в интервала [0 … 100] и бутон [Fire Top] за изпращане на данните от формата към сървъра. Действието, което ще обработи данните, се казва /FireTop
, което означава функция fire_top()
, която се намира във файла app.py. Следват още две подобни форми с бутони [Fire Bottom] и [New Game].
Сега трябва да подготвим плодовете за рисуване в изгледа (view). Добавяме следния код в app.py файла:
Горният код дефинира полета за брой редове, брой колони, за таблицата с плодовете (игралното поле), за натрупаните от играча точки и информация дали играта е активна или е свършила (поле gameOver
). Игралното поле е с размери 9 колони на 3 реда и съдържа за всяко поле текст какво има в него: apple
, banana
, orange
, kiwi
, empty
или dynamite
. Главното действие index()
подготвя игралното поле за чертане като записва елементите на играта и извиква изгледа, който ги чертае в страницата на играта (в уеб браузъра като HTML).
Трябва да генерираме случайни плодове. За да направим това, трябва да напишем метод generate_random_fruits()
с кода от картинката по-долу. Този код записва в таблицата (матрицата) fruits
имена на различни картинки и така изгражда игралното поле. Във всяка клетка от таблицата се записва една от следните стойности: apple
, banana
, orange
, kiwi
, empty
или dynamite
. След това, за да се нарисува съответното изображение в изгледа, към текста от таблицата ще се долепи .png
и така ще се получи името на файла с картинката, която да се вмъкне в HTML страницата като част от игралното поле. Попълването на игралното поле (9 колони с по 3 реда) става в изгледа index.html
, с два вложени for
цикъла (за ред и за колона).
За да се генерират случайни плодове, за всяка клетка се генерира случайно число между 0 и 8 (вж. класа random
в Python). Ако числото e 0 или 1, се слага аpple
, ако е между 2 и 3, се слага banana
и т.н. Ако числото е 8, се поставя dynamite
. Така плодовете се появяват 2 пъти по-често отколкото динамита. Ето и кода:
Чертане на плодовете в index.html
:
За да попълним игралното поле с плодовете, трябва да завъртим два вложени цикъла (за редовете и за колоните). Всеки ред се състои от 9 на брой картинки, всяка от които съдържа apple
, banana
или друг плод, или празно поле empty
, или dynamite
. Картинките се чертаят като се отпечата HTML таг за вмъкване на картинка: <img src="/images/apple.png" />
. Девет картинки се подреждат една след друга на всеки от редовете, а след тях се преминава на нов ред с <br>
. Това се повтаря три пъти за трите реда. Накрая се отпечатват точките на играча. Ето как изглежда кодът за чертане на игралното поле и точките:
Обърнете внимание на къдравите скоби – те служат за превключване между езика HTML и езика Python и идват от Jinja2 синтаксиса за рисуване на динамични уеб страници.
Стартираме проекта с [Shift+F10]. Очаква се да бъде генерирано случайно игрово поле с плодове с размери 9 на 3 и да се визуализира в уеб страницата чрез поредица картинки:
Сега играта е донякъде направена: игралното поле се генерира случайно и се визуализира успешно (ако не сте допуснали грешка някъде). Остава да реализираме същината на играта: стрелянето по плодовете.
За целта добавяме действията [New Game], [Fire Top] и [Fire Bottom] във файла app.py:
Чрез горния код дефинираме три действия:
reset()
– стартира нова игра, като генерира ново случайно игрално поле с плодове и експлозиви, нулира точките на играча и прави играта валидна (gameOver = false
). Това действие е доста просто и може да се тества веднага с [Shift+F10], преди да се напишат другите.fire_top()
– стреля по ред 0 на позицияposition
(число от 0 до 100), взета от потребителя. Извиква се стреляне в посока надолу (+1) от ред 0 (най-горния). Самото стреляне е по-сложно като логика и ще бъде разгледано след малко.fire_bottom()
– стреля по ред 2 на позицияposition
(число от 0 до 100), взета от потребителя. Извиква се стреляне в посока нагоре (-1) от ред 2 (най-долния).
Имплементираме "стрелянето" – метода fire(position, start_row, step)
:
Стрелянето работи по следния начин: първо се изчислява номера на колоната col
, към която играчът се е прицелил. Входното число от скролера (между 0 и 100) се намаля до число между 0 и 8 (за всяка от 9-те колони). Номерът на реда row е или 0 (ако изстрелът е отгоре) или броят редове минус едно (ако изстрелът е отдолу). Съответно посоката на стрелба (стъпката) е 1 (надолу) или -1 (нагоре).
За да се намери къде изстрелът поразява плод или динамит, се преминава в цикъл през всички клетки от игралното поле в прицелената колона и от първия до последния атакуван ред. Ако се срещне плод, той изчезва (замества се с empty
) и се дават точки на играча. Ако се срещне dynamite
, играта се отбелязва като свършила.
Оставаме на по-запалените читатели да имплементират по-сложно поведение, например да се дават различни точки при уцелване на различен плод, да се реализира анимация с експлозия (това не е твърде лесно), да се взимат точки при излишно стреляне в празна колона и подобни.
Тестваме какво работи до момента като стартираме приложението с [Ctrl + Shift + F10]:
- Нова игра → бутонът за нова игра трябва да генерира ново игрално поле със случайно разположени плодове и експлозиви и да нулира точките на играча.
- Стреляне отгоре → стрелянето отгоре трябва да премахва най-горния плод в уцелената колона или да предизвиква край на играта при динамит. Всъщност при край на играта все още нищо няма да се случва, защото в изгледа този случай още не се разглежда.
- Стреляне отдолу → стрелянето отдолу трябва да премахва най-долния плод в уцелената колона или да прекратява играта при уцелване на динамит.
За момента при "Край на играта" нищо не се случва. Ако играчът уцели динамит, в приложението се отбелязва, че играта е свършила (game_оver = Тrue
), но този факт не се визуализира по никакъв начин. За да заработи приключването на играта, е необходимо да добавим няколко проверки в изгледа:
Кодът по-горе проверява дали е свършила играта и показва съответно контролите за стреляне и игралното поле (при активна игра) или картинка с експлодирали плодове при край на играта.
След промяната в кода на изгледа стартираме с [Ctrl + Shift + F10] и тестваме играта отново:
Този път при уцелване на динамит, трябва да се появи дясната картинка и да се позволява единствено действието "нова игра" (бутонът [New Game]).
Сложно ли беше? Успяхте ли да направите играта? Ако не сте успели, не се притеснявайте, това е сравнително сложен проект, който включва голяма доза неизучавана материя. Ако срещнете някакви затруднения, може да питате във форума на СофтУни: https://softuni.bg/forum.