You are on page 1of 366

МІНІСТЕРСТВО ОСВІТИ І НАУКИ УКРАЇНИ

КИЇВСЬКИЙ НАЦІОНАЛЬНИЙ УНІВЕРСИТЕТ ІМЕНІ ТАРАСА ШЕВЧЕНКА

О. С. Бичков
М. В. Ткаченко

Практичне
програмування
мовою Python
Пiдручник
УДК 519.85:004.43(075.8)
Б67

Рецензенти:
д-р техн. наук, проф. В . В . В и ш н е в с ь к и й ,
д-р техн. наук, проф. В . В . О н и щ е н к о

Рекомендовано до друку вченою радою


факультету інформаційних технологій
(протокол № 3 від 16 листопада 2018 року)

Ухвалено науково-методичною радою


Київського національного університету імені Тараса Шевченка
(протокол № 5-18/19 н. р. від 27 березня 2019 року)

Бичков О. С.
Б67 Практичне програмування мовою Python : підручник
/ О. С. Бичков, М. В. Ткаченко. – К. : ВПЦ "Київський
університет", 2019. – 365 с.
ISBN 978-966-933-064-2

Викладено основи програмування мовою Python. Розглянуто основи


синтаксичних конструкцій мови, об'єктно-орієнтованого програмування,
а також алгоритми оброблення iнформації на простих і структурованих типах
даних. Теоретичний матеріал проілюстровано великою кількістю прикладів.
Для студентів спеціальностей "Інженерія програмного забезпечення",
"Прикладна математика", "Інформатика".

УДК 519.85:004.43(075.8)

ISBN 978-966-933-064-2 © Бичков О. С., Ткаченко М. В., 2019


© Київський національний університет імені Тараса Шевченка,
ВПЦ "Київський університет", 2019
ПЕРЕДМОВА

Мова Python1 є, мабуть, найпростішою для вивчення і най-


зручнішою серед поширених мов програмування. Програмний
код у Python легко читати й писати: він лаконічний і не зда-
ється загадковим. Python – дуже виразна мова, що дозволяє
вмістити програму в меншу кількість рядків, ніж це треба
було б в інших мовах, наприклад C++ або Java.
Python є кросплатформною мовою: зазвичай та сама про-
грама мовою Python може запускатися й у Windows, і в UNIX-
подібних системах, таких як Linux, BSD та Mac OS, для чого
достатньо просто скопіювати файл або файли, складові про-
грами на комп'ютер; при цьому навіть не треба виконувати
"збирання" або компіляцію програми. Можна написати мовою
Python програму, яка буде використовувати деякі характерні
особливості конкретної операційної системи, але така необ-
хідність виникає вкрай рідко, оскільки практично вся станда-
ртна бібліотека Python і більшість бібліотек сторонніх вироб-
ників забезпечують повну кросплатформність.
Підручник складається із двох розділів. У першому розділі
наведено основи програмування мовою Python, у другому –
увага зосереджена на особливостях розв'язання деяких мате-
матичних задач за допомогою цієї мови.
Книга призначена для читача з деяким досвідом програму-
вання (будь-якою мовою). Тому в другому розділі підручни-
ка, що присвячений програмуванню розв'язання деяких мате-
матичних задач, код програм зображений у вигляді псевдоко-
ду, а також мовами С++ та Python, що, на думку авторів, дає
змогу проводити порівняльний аналіз коду і більш поглибле-
но освоювати мову програмування Python.
1
Гвідо ван Россум, творець мови Python, назвав її так на честь те-
лешоу на BBC під назвою "Літаючий цирк Монті Пайтона" (Monty
Python's Flying Circus).
3
РОЗДІЛ 1
Вступ до програмування
мовою Python

1.1. Особливості мови Python


Проста. Python – мова проста і мінімалістична. Читання хо-
рошої програми мовою Python дуже нагадує читання англійсь-
кого тексту, хоча й досить "суворого"! Така псевдокодова при-
рода Python є однією з її найсильніших сторін. Вона дозволяє
зосередитися на розв'язанні завдання, а не на самій мові.
Вільна і відкрита. Python – це приклад вільного і відкритого
програмного забезпечення. Простіше кажучи, ви маєте право вільно
поширювати копії цього програмного забезпечення, читати його
вихідні тексти, вносити зміни, а також використовувати його части-
ни у своїх програмах. В основі вільного програмного забезпечення
лежить ідея спільноти, яка ділиться своїми знаннями. Це одна з при-
чин, чому мова Python така популярна: вона була створена і постійно
поліпшується співтовариством, яке просто хоче зробити її краще.
Мова високого рівня. При написанні програми мовою
Python вам ніколи не доведеться відволікатися на такі низькорі-
вневі деталі, як керування пам'яттю, що використовується ва-
шою програмою, тощо.
Можливість перенесення коду. Завдяки відкритій природі
Python була портована на багато платформ (тобто змінена таким
чином, щоб працювати на них). Усі програми можуть запускатися
на будь-якій із цих платформ без будь-яких змін, якщо тільки ви
уникали використання системно-залежних функцій.
Python можна використовувати в будь-якій операційній сис-
темі й на будь-якому пристрої.
Можливість інтерпретації. Програма, написана мовою про-
грамування C або C++, компілюється, перетворюється з вихідної
мови на мову, зрозумілу комп'ютеру (бінарний код, тобто нулі
та одиниці), за допомогою компілятора із застосуванням різно-
манітних прапорців і параметрів.
4
Коли ви запускаєте таку програму, компонувальник або заван-
тажувач копіює програму з диска в оперативну пам'ять і запускає
її. Python, навпаки, не вимагає компіляції в бінарний код. Програма
просто виконується з вихідного тексту. Python самостійно перетво-
рює вихідний текст на деяку проміжну форму, так званий байткод,
а потім переводить його на машинну мову і запускає. Усе це помі-
тно полегшує використання Python, оскільки немає необхідності
піклуватися про компіляцію програми, підключення й завантажен-
ня потрібних бібліотек тощо. Разом з тим це робить програми мо-
вою Python доволі прийнятними, оскільки достатньо їх просто ско-
піювати на інший комп'ютер, і вони працюють!
Об'єктно-орієнтована. Python підтримує як процедурно-орієн-
товане, так і об'єктно-орієнтоване програмування (ООП). У проце-
дурно-орієнтованих мовах програми будуються на основі процедур
або функцій, які є багаторазово використовуваними фрагментами
програми. В об'єктно-орієнтованих мовах програмування програми
будуються на основі об'єктів, які об'єднують дані та функціонал.
Python надає прості, але потужні засоби для ООП, особливо порів-
няно з такими великими мовами програмування, як C++ або Java.
Розширюваність. Якщо треба, щоб деяка критична частина
програми працювала дуже швидко або приховати частину алго-
ритму, то можна написати цю частину програми мовою C або
C++, а потім викликати її з програми мовою Python.
Вбудованість. Python можна вбудовувати в програми
C/C++ для надання можливості написання сценаріїв їхнім
користувачам.
Великі бібліотеки. Стандартна бібліотека Python дуже велика.
Вона може допомогти при розв'язанні найрізноманітніших завдань,
пов'язаних із використанням регулярних виразів, генеруванням
документації, перевіркою блоків коду, розпаралелюванням проце-
сів, базами даних, веб-браузерами, CGI2, FTP3, електронною по-
штою, файлами XML4, XML-RPC5, HTML6, WAV7, криптографі-

2
CGI (Common Gateway Interface – "загальний інтерфейс шлюзу") – ста-
ндарт інтерфейсу, що використовується для зв'язку зовнішньої програми з
веб-сервером.
3
FTP (File Transfer Protocol) – один з базових протоколів передачі
файлів, призначений для передачі файлів у мережі між комп'ютерами.
4
XML (Extensible Markup Language) – розширювана мова розмітки.
5
єю, GUI8 та іншими системно-залежними процесами та об'єкта-
ми. Пам'ятайте, що все це є на кожному кроці, де встановлена
Python. У цьому полягає філософія Python: "Усе включено" (All
inclusive).
Крім стандартної бібліотеки, існує безліч інших високоякіс-
них бібліотек, які можна знайти в каталозі пакетів Python.

1.2. Дзен Python


Якщо інтерпретатору Python дати команду import this (імпор-
тувати саме об'єкт), то виведеться Дзен Python, що ілюструє
ідеологію й особливості зазначеної мови:

Фраза Переклад
1. Beautiful is better than ugly Красиве краще за потворне
2. Explicit is better than implicit Просте краще за складне
3. Complex is better than
Складне краще за ускладнене
complicated
4. Flat is better than nested Плоске краще ніж вкладене
5. Sparse is better than dense Розріджене краще ніж щільне
6. Readability counts Читабельність важлива
7. Special cases aren't special Виняткові випадки не настільки
enough to break the rules важливі, щоб порушувати правила
Однак практичність важливіша
8. Although practicality beats purity.
за чистоту
Помилки ніколи не мають замо-
9. Errors should never pass silently
вчуватися

5
XML-RPC (Extensible Markup Language Remote Procedure Call – XML-
виклик віддалених процедур) – визначає набір стандартних типів даних і
команд, які програміст може використовувати для доступу до функціона-
льності іншої програми, що міститься на іншому комп'ютері в мережі.
6
HTML (Hyper Text Markup Language – мова розмітки гіпертексто-
вих документів) – стандартна мова розмітки веб-сторінок в Інтернеті.
7
WAV (wave) – формат аудіофайлу, розроблений компаніями Mi-
crosoft та IBM.
8
GUI (Graphical user interface – графічний інтерфейс користувача) –
тип інтерфейсу, який дозволяє користувачам взаємодіяти з електро-
нними пристроями через графічні зображення та візуальні вказівки.
6
Фраза Переклад
За винятком замовчування, яке
10. Unless explicitly silenced
задане спеціально
11. In the face of ambiguity, refuse У випадку неоднозначності не
the temptation to guess піддавайтеся спокусі вгадати
12. There should be one – and prefe- Має існувати один і, бажано, тільки
rably only one – obvious way to do it один очевидний спосіб зробити це
Хоча він може бути з першого
13. Although that way may not be
погляду не очевидний, якщо ти
obvious at first unless you're Dutch
не голландець
14. Now is better than never Зараз краще, ніж ніколи
15. Although never is often better Проте ніколи частіше краще, ніж
than * Right * now прямо зараз
16. If the implementation is hard to Якщо реалізацію складно пояс-
explain, it's a bad idea нити – це погана ідея
17. If the implementation is easy to Якщо реалізацію легко пояснити
explain, it may be a good idea – це може бути хороша ідея
18. Namespaces are one honking Простори назв – прекрасна ідея,
great idea – Let's do more of those! давайте робити їх більше!

Глибоке розуміння цього приходить до тих, хто зможе освої-


ти мову Python повною мірою й отримає досвід практичного
програмування.

1.3. Основи синтаксису Python


1.3.1. Коментарі

Коментарі – це те, що пишеться після символу #9 і становить


інтерес лише як замітка для читача програми, наприклад:
print ('Привіт, Світ!) # print – це функція
або:
#print – це функція
print (''Привіт, Світ!)

9
"#" – знак решітки. Інші назви: "ґратка", "геш"/"хеш", "дієз", "шарп",
"знак номера", "клоп".
7
Намагайтеся у своїх програмах писати якомога більше кори-
сних коментарів, що пояснюють:
• припущення;
• важливі розв'язання;
• важливі деталі;
• проблеми, які ви намагаєтеся розв'язати;
• проблеми, яких ви намагаєтеся уникнути тощо.
Текст програми пояснює ЯК, а коментарі мають пояснювати
ЧОМУ. Це буде корисним для тих, хто читатиме вашу програму,
оскільки їм легше буде зрозуміти, що програма робить.

1.3.2. Літеральні константи

Прикладом літеральної константи може бути число, наприклад


5, 0.0925, 9.25Е-3,
або що-небудь на зразок
'Це рядок',
або
"It's a string!".
Вони називаються літеральними, тому що їхні значення ви-
користовуються літерально. Число 2 завжди зображує лише себе
і нічого іншого – це константа, тому що його значення не можна
змінити. Звідси походить назва "літеральні константи".

1.3.3. Числа

Числа в Python бувають трьох типів: цілі, з плаваючою точ-


кою та комплексні.
Прикладами чисел із плаваючою точкою (для стислості –
плаваючих чисел) можуть бути 3.23 та 52.3E-4. Позначення E
показує степінь числа 10. У даному випадку 52.3E-4 означає
52.3 × 10-4.
Приклади комплексних чисел: (-5 + 4j) і (2.3 – 4.6j).

8
1.3.4. Рядки

Рядок – це послідовність символів. Найчастіше рядки – це


просто деякі набори слів.
Слова можуть писатися як англійською мовою, так і будь-
якою іншою, що підтримується стандартом Unicode, тобто май-
же будь-якою мовою світу.
Одинарні лапки. Рядок можна вказати, використовуючи оди-
нарні лапки, наприклад 'Фраза в лапках'. Усі пробіли та знаки
табуляції збережуться.
Подвійні лапки. Рядки в подвійних лапках працюють так са-
мо, як і в одинарних. Наприклад: "What's your name?".
Потрійні лапки. Можна вказувати "багаторядкові" рядки з ви-
користанням потрійних лапок. У межах потрійних лапок можна
вільно використовувати подвійні або потрійні лапки, наприклад:
'''Це багаторядковий рядок. Це її перший рядок.
Це її другий рядок.
"What's your name?", – запитав я.
Він відповів: "Bond, James Bond."
'''
Зауваження для програмістів мовою C++: у Python немає
окремого типу даних char (символ).
Об'єднання рядкових констант. Якщо розташувати поруч
дві рядкові константи, то Python автоматично їх об'єднає. На-
приклад (зверніть увагу на символ "\"10, який використовується в
даному прикладі для відокремлення одинарних лапок від апо-
строфа; "␣" – символ пробілу):
'What\'s␣' 'your␣name?'
автоматично перетворюється на
"What's␣your␣name?"

Пам'ятайте, що рядки в подвійних і одинарних лапках еквіва-


лентні й нічим один від одного не відрізняються.

10
"\" – back slash – обернена коса риска.
9
Метод format. Призначений для виведення форматованих
даних. Розглянемо такий код:
age = 26
name = 'Swaroop'
print('Вік {0} – {1} років.'.format(name, age))
print('Чому {0} бавиться з цим Python?'.format(name))
Виведення:
$ python str_format.py
Вік Swaroop – 26 років.
Чому Swaroop бавиться з цим Python?

Як це працює. У рядок можуть бути включені певні позна-


чення, а згодом викликаний метод format для заміщення цих
позначень на відповідні аргументи.
Погляньте на перший випадок застосування позначень, де ми
пишемо {0}. Це відповідає змінній name, що є першим аргумен-
том методу format. Аналогічно друге позначення {1} відповідає
змінній age, що є другим аргументом методу format. Зауважте,
що Python починає відлік з 0, тому перша позиція – номер 0,
друга – номер 1 і т. д.
Можна отримати той самий результат і об'єднанням рядків:
'Вік' + name + ' – ' + str (age) + ' років.',
однак у такому разі легко припуститися помилки.
Перетворення на рядок виконується методом format автома-
тично, на відміну від явного перетворення в нашому прикладі.
Використовуючи метод format, ми можемо змінити повідомлен-
ня, не зачіпаючи використовуваних змінних, і навпаки.
Зауважимо, що цифри тут не обов'язкові. Можна було б про-
сто написати:
age = 26
name = 'Swaroop'
print('Вік {} – {} років.'.format(name, age))
print('Чому {} бавиться з цим Python?'.format(name))
і отримати такий самий результат, як і раніше. У методі format
Python поміщає значення кожного аргументу в зазначене місце.

10
1.3.5. Змінні

Слово "змінні" говорить саме за себе – їхнє значення може зміню-


ватися, а значить, можна зберігати у змінній усе що завгодно. Змінні
– це просто ділянки пам'яті комп'ютера, у яких зберігається деяка
інформація. На відміну від констант, до такої інформації потрібно
якимось чином отримувати доступ, тому змінним дають імена.
Змінні – це окремий випадок ідентифікаторів. Ідентифікатори
– це імена, що присвоєні чомусь для позначення. При виборі імен
для ідентифікаторів необхідно дотримуватися таких правил:
• Першим символом ідентифікатора має бути літера з алфаві-
ту (символ АSCII11 у верхньому або нижньому регістрі або сим-
вол Unicode12), а також символ підкреслення ("_").
• Інша частина ідентифікатора може складатися з літер (сим-
вол АSCII у верхньому або нижньому регістрі або символ
Unicode), знаків підкреслення ("_") або цифр (0–9).
• Імена ідентифікаторів чутливі до регістру.
Змінні можуть зберігати значення різних типів, що назива-
ються типами даних. Основними типами є числа і рядки, про які
ми вже говорили. Далі ви дізнаєтеся, як створювати власні типи
за допомогою класів.

1.3.6. Об'єкти

Пам'ятайте, Python розглядає все, що є в програмі, як об'єкти


(у найзагальнішому сенсі). Замість того, щоб казати "щось",
треба казати "об'єкт".
Зауваження для програмістів мовою C++: мова Python строго
об'єктно-орієнтована в тому сенсі, що об'єктом є все, включаю-
чи числа, рядки та функції.

11
ASCII (American Standard Code for Information Interchange – Аме-
риканський стандартний код для інформаційного обміну) – система
кодів, у якій числа від 0 до 127 включно поставлені у відповідність до
літер, цифр і символів пунктуації.
12
Unicode (Юніко́д), УНІфіковане КОДування – промисловий стан-
дарт, розроблений, щоб забезпечити цифрове зображення символів
усіх писемностей світу та спеціальних символів.
11
Розглянемо такий приклад використання змінних і констант:
i=5
print(i)
i=i+1#i+=1
print(i)
s = '''Це багаторядковий рядок.
Це другий її рядок.'''
print(s)
Виведення:
5
6
Це багаторядковий рядок.
Це другий її рядок.
Як це працює. Спочатку присвоюємо значення константи 5
змінній i, використовуючи оператор присвоювання (=). Цей ря-
док називається пропозицією й указує, що має бути виконана
деяка дія. У цьому випадку ми пов'язуємо ім'я змінної зі значен-
ням 5. Потім друкуємо значення i, використовуючи функцію
print, яка просто друкує значення змінної на екрані.
Далі додаємо 1 до значення, що зберігається в i, та зберігаємо
його там. Після цього друкуємо його та отримуємо значення 6,
що не дивно. Аналогічно присвоюємо рядкову константу змін-
ній s, після чого друкуємо.
Зауваження для програмістів мовою C++: змінні використо-
вуються простим присвоюванням їм значень. Ніякого попере-
днього оголошення або визначення типу даних не потрібно (ди-
намічне визначення типу змінних).
Розглянемо особливості роботи Python зі змінними на при-
кладі такого коду (переведення зі шкали градусів за Цельсієм у
шкалу градусів за Фаренгейтом):
>>> cel = 26
>>> cel
26 cel id1 : int
>>> 9/5 * cel + 32
78.80000000000001 id1 26
>>>
12
У момент виконання присвоювання cel = 26 у пам'яті комп'ю-
тера створюється об'єкт, розміщений за деякою адресою13 (умо-
вно позначимо його id1), що має значення 26 цілочислового
типу int. Потім створюється змінна з ім'ям cel, якій присвоюєть-
ся адреса об'єкта id1. Змінні в Python містять адреси об'єктів,
інакше можна сказати, що змінні посилаються на об'єкти. По-
стійно зберігаючи в пам'яті цю модель, для спрощення будемо
говорити, що змінна містить значення.
Обчислення наступного виразу зумовить присвоювання
змінній cel значення 72, тобто спочатку обчислюється права
частина, потім результат присвоюється лівій частині:
>>> cel = 26 + 46
>>> cel
72
>>>
Розглянемо трохи складніший приклад. Замість змінної diff
підставимо цілочислове значення 20:
>>> diff = 20
>>> double = 2 * diff
>>> double
40
>>>
Після закінчення обчислень пам'ять для Python матиме такий
вигляд (рис. 1.1):

Рис. 1.1

13
Інформація для досвідчених програмістів. Функція id () повертає
ідентифікатор об'єкта, переданого як аргумент функції. У реалізації
Python число, що повертається, є адресою об'єкта в пам'яті.
13
Продовжимо обчислення. Дамо змінній diff значення 5 і роз-
глянемо зміст змінних double та diff.
>>> diff = 5
>>> double
40
>>> diff
5
У момент присвоювання змінній diff значення 5 у пам'яті ство-
риться об'єкт за адресою id3, що містить цілочислове значення 5.
Після цього зміниться зміст змінної diff, замість адреси id1 туди
запишеться адреса id3 (рис. 1.2). Також Python побачить, що на
об'єкт за адресою id1 більше ніхто не посилається, і видалить його
з пам'яті14 (автоматичне "прибирання" сміття). При цьому Python
не змінюватиме існуючі числові об'єкти, а створить нові15 (особли-
вість числового типу даних – його об'єкти є незмінними).
14
Поточну кількість посилань на об'єкт можна дізнатися за допомогою
функції
sys.getrefcount ():
>>> s=36
>>> import sys
>>> sys.getrefcount(s)
13
>>>
15
Інформація для досвідчених програмістів. Для економії ресурсів при
роботі з невеликими цілими значеннями Python посилається на вже існую-
чі в пам'яті об'єкти:
>>> i=3
>>> j=3
>>> k=4-1
>>> id(i)
1647399712
>>> id(j)
1647399712
>>> id(k)
1647399712
>>> i=3000000000
>>> j=3000000000
>>> id(i)
13786168
>>> id(j)
49195536
>>>
14
Рис. 1.2

1.3.7. Логічні та фізичні рядки

Фізичний рядок – це те, що ви бачите, коли набираєте про-


граму. Логічний рядок – це те, що Python бачить як єдине ре-
чення. Python неявно передбачає, що кожному фізичному рядку
відповідає логічний рядок.
Прикладом логічного рядка може служити речення
print ('Привіт, світ!');
якщо воно розміщується на одному рядку, то цей рядок відпові-
дає фізичному рядку. Python неявно стимулює використання по
одному реченню на рядок, що полегшує читання коду.
Щоб записати більше одного логічного рядка на одному фі-
зичному рядку, необхідно явно вказати це за допомогою крапки
з комою (;), яка позначає кінець логічного рядка або речення.
Наприклад:

Звичайний З використанням Запис в один рядок


запис символу ";" з використанням символу ";"
i=5 i = 5;
i = 5;print(i); i = 5;print(i)
print(i) print(i);

Однак наполегливо рекомендується дотримуватися написан-


ня одного логічного рядка в кожному фізичному рядку. Таким
чином ви можете обійтися зовсім без крапки з комою.

15
Можна використовувати більше одного фізичного рядка для
логічного рядка, але до цього слід удаватися лише в разі дуже
довгих рядків. Приклад написання одного логічного рядка, що
займає кілька фізичних рядків, наведено нижче. Це називається
явним об'єднанням рядків.
s = 'Це рядок. \
Це рядок продовжується.'
print(s)
Маємо результат:
Це рядок. Це рядок продовжується.
Аналогічно
print \
(i)
є тим самим, що
print (i)
Іноді має місце неявне використання оберненої косої риски
("\"). Це стосується випадків, коли в логічному рядку є відкрита
кругла ("("), квадратна ("[") або фігурна ("{") дужка, але немає
дужки, що закриває. Така ситуація називається неявним об'єд-
нанням рядків.

1.3.8. Відступи

У Python пробіли важливі. Точніше, пробіли важливі на по-


чатку рядка. Вони називаються відступами. Відступи (пробіли
й табуляції) на початку логічного рядка використовуються для
групування речень. Це означає, що речення, які йдуть разом,
повинні мати однаковий відступ. Кожен такий набір речень на-
зивається блоком. Неправильні відступи можуть призводити до
виникнення помилок. Наприклад:
i=5
˽print ('Значення становить', i) # Помилка! Пробіл на початку
рядка
print ('Я повторюю, значення становить ', i)
Коли ви запустите це, отримаєте таку помилку:
File "whitespace.py", line 4

16
˽print ('Значення становить ', i)# Помилка! Пробіл на початку
рядка
^
IndentationError: unexpected indent
Зверніть увагу, що на початку другого рядка є один пробіл
("˽"). Помилка, відображена Python, свідчить про те, що синтак-
сис програми є неправильним, тобто програма не була написана
за правилами. Це означає, що ви не можете починати нові блоки
речень де завгодно (крім основного блоку за умовчанням, який
використовується в усій програмі).

Вправи
1. Арифметичні операції над двома числами. Програма вико-
нує різні математичні дії над двома числами, виводячи на екран
розгорнуту форму запису дії.
На вході: два цілих додатних числа – А та В.
На виході: рядок, що містить цілі (або дійсні) числа:

1 Сума двох чисел: А+В 8. Різниця двох чисел: А–В


Піднесення до куба різниці Піднесення до квадрата резу-
2 9.
двох чисел: (А–В)3 льтату ділення двох чисел: (А/В)2
Піднесення до суми двох
3 Добуток двох чисел: А×В 10.
чисел: (А+В)3
Піднесення числа до степе- Піднесення до куба результа-
4 11.
ня: АВ ту ділення двох чисел: (А/В)3
Піднесення до квадрата Піднесення до куба добутку
5 12.
суми двох чисел: (А+В)2 двох чисел: (А×В)3
Піднесення до квадрата
6 13. Ділення двох чисел: (А/В)
різниці двох чисел: (А–В)2
Піднесення до квадрата Експонента суми чисел:
7 14.
добутку двох чисел: (А×В)2 е(А+В)
Перше число: номер групи.
Друге число: номер студента 11 + 7 = 18
в журналі

2. Текстовий рисунок. Програма виводить на екран один з те-


кстових рисунків.
На виході: кілька рядків.
17
С 888 888
Ж 888888888 /~~~\
/\_/\
ЖЖЖ (\ /) 888888888 //^ ^\\
>^,^<
ЖЖЖЖЖ (* *) 8888888 (/(_&_)\)
/\
ЖЖЖЖЖЖЖ C(")(") 88888 _/''*''\_
(|_|}_/
НН НН 888 (/_)^(_\)
ZZZZZ 8
3. Виведення тексту драбинкою. Програма друкує на екрані
драбинкою введений із клавіатури текст. Пробіли використову-
вати не можна, замість цього застосуйте функції вирівнювання
тексту по боках.
На вході: три рядкові змінні.
На виході: кілька рядків.
Суб'єкт: ім'я студента. Ім'я студента
Дія: вивчив. вивчив
Об'єкт: Python Python
4. Переможці змагань. Програма зчитує дані про результати
змагань (зайняті переможцями місця) і виводить текст, аналогі-
чний наведеному в прикладі (пробіли важливі!).
На вході: три рядкові змінні.
На виході: кілька рядків.
Третє місце: ім'я 1 (будь-яке ім'я). __Ім'я 3__
Друге місце: ім'я 2. 1 |__Ім'я 2__
Перше місце: ім'я 3 2 |__Ім'я 1__
3 |
5. Кидання костей. Програма моделює кидання двох шести-
гранних костей (за допомогою генератора випадкових чисел) з
обчисленням суми очок. На екран виводяться значення очок, що
випали для кожної кістки, а також їхня сума.
На вході: нічого.
На виході: кілька рядків, що містять цілі числа.
import random Перша кість: 4
⁞ Друга кість: 5
ім'я_змінної = random.randint(…) Сума очок: 9
або
ім'я_змінної = random.randrange(…)
18
1.4. Оператори та вирази
Більшість речень (логічних рядків) у програмах містять вира-
зи. Простий приклад виразу: 2 + 3. Вираз можна поділити на
оператори й операнди.

1.4.1. Оператори

Оператори – це деякий функціонал, що виконує будь-які дії


та може бути зображений у вигляді символів, наприклад симво-
лу "+" або спеціальних зарезервованих слів. Оператори можуть
виконувати деякі дії над даними; такі дані називаються операн-
дами. У нашому випадку 2 та 3 – це операнди.
У математичних виразах як операнди можна використовува-
ти цілі числа16 (1, 4, -5) або дійсні (у програмуванні їх ще нази-
вають числами з плаваючою точкою): 4.111, -9.3. Математичні
оператори, що доступні над числами в Python17:
Оператор Опис
+ Додавання
- Віднімання
* Множення
/ Ділення (результат – дійсне число)
// Цілочислове ділення
% Залишок від ділення
** Піднесення до степеня

Зауваження для програмістів на C++: числа з плаваючою то-


чкою в Python зображені звичайними числами з плаваючою точ-
кою подвійної точності (64 біта). Зазвичай це зображення відпо-
відає стандарту IEEE 754, який дозволяє забезпечувати зобра-
ження приблизно 17 значущих розрядів з експонентою в діапа-
зоні від -308 до 308. Це повністю відповідає типу double в мові
C++.
16
Також комплексні числа, логічні значення: True, False.
17
Цікаво, що в Python вираз (b * (a // b) + a% b) еквівалентний а.
19
Скорочений запис математичних операцій та присвою-
вання. Часто результат якоїсь математичної операції необхідно
присвоїти змінній, над якою ця операція виконувалася. Для цьо-
го існують короткі форми запису виразів. Ви можете записати:
a = 2; a = a * 3
у вигляді
a = 2; a * = 3
Зверніть увагу, що вирази типу "змінна = змінна операція ви-
раз" набувають вигляду "змінна операція = вираз".

1.4.2. Порядок обчислення

Якщо є вираз вигляду 2 + 3 * 4, то яка операція здійснюється ра-


ніше: додавання або множення? Згідно зі шкільним курсом матема-
тики множення має виконуватись у першу чергу. Це означає, що
оператор множення має вищий пріоритет ніж оператор додавання.
Наведена нижче таблиця відображує пріоритет операторів у
Python від найнижчого (найслабше зв'язування) до найвищого
(найсильніше зв'язування). Це означає, що в будь-якому виразі
Python спочатку обчислює оператори та вирази, розташовані
внизу таблиці, а потім – уверх по таблиці.
Оператор Опис
or Логічне "АБО"
and Логічне "І"
not Логічне "Ні"
<, <=, >, >=, !=, == Порівняння
+, - Додавання, віднімання
Множення, ділення, цілочислове ділення, за-
*, /, //, %
лишок від ділення
** Піднесення до степеня

У цій таблиці оператори з однаковим пріоритетом розташовані


в одному рядку. Наприклад, "+" та "-" мають однаковий пріоритет.
На практиці краще використовувати дужки для групування
операторів і операндів, щоб у явному вигляді вказати порядок
обчислення виразів. Також це полегшить читання програми.
20
1.4.3. Зміна порядку обчислення

Для полегшення читання виразів можна використовувати


дужки. Наприклад, 2 + (3 * 4) легше зрозуміти, ніж 2 + 3 * 4, що
вимагає знання пріоритету операторів.
Є ще одна перевага у використанні дужок – вони дають мож-
ливість змінити порядок обчислення виразів. Наприклад, якщо
додавання необхідно виконати раніше множення, то можна за-
писати щось на зразок (2 + 3) * 4.

1.4.4. Асоціативність

Оператори зазвичай обробляються зліва направо. Це означає,


що оператори з однаковим пріоритетом будуть оброблені по
порядку від лівого до правого. Наприклад, 2 + 3 + 4 обробляєть-
ся як (2 + 3) + 4. Деякі оператори, наприклад оператор присвою-
вання, мають асоціативність справа наліво, тобто a = b = c роз-
глядається як a = (b = c).

Вправи
1. Сума цифр. Програма обчислює суму цифр s у заданому
числі n.
На вході: ціле тризначне число n.
На виході: ціле число s (min ≤ n ≤ max). Додатково визначити
значення min та max.
Введіть число: 456 Сума цифр числа 456 становить 15.
Min <= 15 <= Max
2. Переведення бітів. Програма переводить ціле число бітів b
у запис типу: "х1 Мбайт х2 Кбайт х3 байт х4 біт".
На вході: ціле число бітів b (наприклад 1234567).
На виході: рядок, що містить цілі числа.
Випадкове число бітів: 1234567 1 Мбайт, 234 Кбайт, 560 байт,
7 біт
Випадкове число бітів: 2345671 2 Мбайт, 345 Кбайт, 670 байт,
1 біт
21
3. Переведення секунд. Програма переводить ціле число се-
кунд s у запис типу: "x1 хвилина х2 секунда".
На вході: ціле число секунд s (1000 ≤ n < 3600).
На виході: рядок, що містить цілі числа.
Випадкове число секунд: 1234 20 хвилин, 34 секунди
4. Електронні годинники. З початку доби минуло n хвилин.
Програма визначає, скільки годин h і хвилин m буде показувати
електронний годинник у цей момент.
Урахуйте, що число n може бути більше кількості хвилин у добі.
На вході: ціле число n (60 < n < 2000).
На виході: рядок, що містить цілі числа h та m (0 ≤ h, m < 60),
роздільник – двокрапка. Якщо значення менше 10, то місце дру-
гої цифри заповнюється нулем.
Випадкове число хвилин: 648 10:48
Випадкове число хвилин: 1505 01:05
5. Різниця часів. Дано значення двох моментів часу, що нале-
жать одній добі: години h1 та h2, хвилини m1 та m2, секунди s1 та
s2, причому другий момент часу настав не раніше першого. Про-
грама визначає, скільки секунд минуло між двома моментами.
На вході: шість цілих чисел: три (h1, m1, s1) для першого і
три (h2, m2, s2) для другого моментів (0≤h1, h2; 0≤m1, m2≤59;
0≤s1, s2≤59).
На виході: ціле число – кількість секунд.
Час початку відліку: 1 1 1 3661 сек
Час кінця відліку: 2 2 2
Час початку відліку: 1 2 30 50 сек
Час кінця відліку: 1 3 20
6. Годинники-1. З початку доби минуло h годин, m хвилин, s
секунд. Програма обчислює кут α (у градусах), на який поверну-
лась годинникова стрілка з початку доби:
m s 360
a = (h + + )× .
60 3600 12
На вході: три цілих числа h, m, s (випадкові) (0≤ h <12, 0≤ m
<60, 0≤ s <60).
22
На виході: одне дійсне число α.
Введіть h, m, s: 1 2 6 31.05 град.
7. Годинники-2. З початку доби годинникова стрілка повер-
нулася на кут αо. Програма визначає, скільки повних годин h,
хвилин m і секунд s минуло з початку доби.
На вході: одне дійсне число α (випадкове) (0≤ α <360).
На виході: рядок, що містить час у форматі "гг: мм: сс".
Введіть α (град.): 31.05 01:02:06
Введіть α (град.): 359.992 11:59:59
Введіть α (град.): 1 00:02:00

1.5. Керувальні структури

1.5.1. Логічні операції та операції порівняння

В усіх мовах програмування високого рівня є можливість ро-


згалуження програми; при цьому виконується одна з гілок про-
грами залежно від виконання або невиконання будь-якої умови.
Перш ніж переходити до інструкції, що здійснює вибір гілки
програми, по якій піде потік виконання, розберемося з логічни-
ми виразами.
Логічними виразами називають вирази, результатом яких є
логічне "так" (True) або логічне "ні" (False). У найпростішому
випадку будь-яке твердження може бути істинним або хибним.
Наприклад, істинність твердження "на вулиці йде дощ" зале-
жить від того, яка на вулиці погода в даний момент; 2 + 2 дорів-
нює 4 – істинний, а 2 + 2 дорівнює 5 – хибний вираз.
Операції порівняння в програмуванні зустрічаються частіше
інших, тому з них ми і почнемо. Для перевірки рівності двох
значень у Python використовується оператор == (два знаки рів-
ності без пробілу між ними).
>>> x = 2 + 2
>>> x == 4

23
True
>>> x == 5
False
>>> x != 5 # x не дорівнює 5
True
>>> x > 5 # x більше 5
False
>>> x < 5 # x менше 5
True
>>> x >= 4 # x більше або дорівнює 4
True
>>> x <= 4 # x менше або дорівнює 4
True
Результат порівняння двох значень ми можемо записати в
змінну:
>>> y = x == 5
>>> print y
False
Бачимо, що пріоритет операцій порівняння менше пріоритету
арифметичних операцій, але більше, ніж в операції присвоювання.

1.5.2. Логічні оператори

Логічні вирази можуть бути складнішими та складатися із кі-


лькох простих. Для об'єднання простих виразів у більш складні
використовуються логічні оператори and, or та not. Значення їх
повністю збігаються зі значеннями англійських слів, якими вони
позначаються. Вираз x and y буде істинним тільки в тому випад-
ку, коли x – істина та y – істина. У всіх інших випадках вираз
буде хибою. Вираз x or y буде істиною, якщо хоча б один з опе-
рандів – істина. Оператор not символізує заперечення: not x –
істина, якщо x – хиба, і навпаки: not x – хиба, якщо x – істина.
Зверніть увагу, що оператор not унарний, тобто працює тільки з
одним операндом.

24
Для простоти складемо таблицю зі списків усіх можливих ком-
бінацій значень операндів логічних виразів і самих виразів (такі
таблиці в математичній логіці називаються таблицями істинності):
х у x and y x or y
0 0 0 0
0 1 0 1
1 0 0 1
1 1 1 1

1.5.3. Виконання за умовою та порожнеча

Розглянемо оператор перевірки умови, що дозволяє організу-


вати розгалуження програми при виконанні тієї чи іншої умови.
Загалом він має такий вигляд:
if ЛОГІЧНА_УМОВА:
....ПОСЛІДОВНІСТЬ_ВИРАЗІВ
Першим іде ключове слово if (англ. "якщо"); за ним – логіч-
ний вираз, потім двокрапка, що позначає кінець заголовка опе-
ратора, а після неї – будь-яка послідовність виразів, або тіло
умовного оператора, що буде виконуватися у випадку, коли
умова в заголовку оператора істинна. Простий приклад:
x=2
if x > 0:
print "x is positive"
if x < 0:
print "x is negative"
print "Stopping..."
Запустимо цю програму і розберемо результат її виконання:
x is positive
Stopping...
У першому рядку ми визначили змінну x, присвоївши їй зна-
чення 2. Потім за допомогою умовного оператора перевірили на
істинність вираз x > 0. Значення x виявилося більше нуля, тому
Python виконав тіло цього умовного оператора: вивів рядок x is

25
positive. Далі йде ще один оператор if, що перевіряє виконання
умови x < 0, яке є хибним, тому тіло цього умовного оператора
Python пропустив і виконав наступну інструкцію.

1.5.4. Альтернативні гілки програми


(Chained conditionals)

Умовний оператор if має розширений формат, що дозволяє


перевіряти кілька незалежних одна від одної умов і виконувати
один із блоків, поставлених у відповідність до цих умов. Загаль-
ний вигляд оператора такий:
if ЛОГІЧНА_УМОВА_1:
....ПОСЛІДОВНІСТЬ_ВИРАЗІВ_1
elif ЛОГІЧНА_УМОВА_2:
....ПОСЛІДОВНІСТЬ_ВИРАЗІВ_2
elif ЛОГІЧНА_УМОВА_3:
....ПОСЛІДОВНІСТЬ_ВИРАЗІВ_3 ...
else:
....ПОСЛІДОВНІСТЬ_ВИРАЗІВ_N
....
Працює ця конструкція таким чином. Спочатку перевіряється
перша умова, якщо ж вона істинна, то виконується перша послідо-
вність виразу. Після цього потік виконання переходить до рядка,
який іде після умовного оператора (тобто за послідовністю виразів
N). Якщо перша умова дорівнює False, то перевіряється друга умо-
ва (наступна після elif); у разі її істинності виконується послідов-
ність 2, а потім знову потік виконання переходить до рядка, що
розташований за оператором умови. Аналогічно перевіряються всі
інші умови. До гілки програми else потік виконання доходить тіль-
ки в тому випадку, якщо не виконується жодна з умов.
Ключове слово elif (англ. "Else if" – "інакше якщо"): умова,
що йде після нього, перевіряється тільки тоді, коли всі попере-
дні умови помилкові.
Щоб було зрозуміліше, напишемо невелику програму, яка ілюс-
трує використання умовного оператора з альтернативними гілками:
choice = raw_input('Input your choice, please (1 or 2): ')
if choice == "1":

26
function1()
elif choice == "2":
function2()
else:
print "Invalid choice!"
print "Thank you."
Спочатку програма просить користувача ввести одиницю або
двійку, потім умовний оператор перевіряє введене значення на
рівність із рядком '1' (функція raw_input () повертає рядкове зна-
чення). Якщо умова істинна, то викликається функція function1
(), після чого програма виводить рядок "Thank you." і завершу-
ється; в іншому випадку значення змінної choice порівнюється з
рядком '2': якщо умова виконується, то викликається функція
function2 (), потім виводиться рядок "Thank you." і програма
завершується; інакше програма виводить повідомлення про те,
що введене значення некоректне, потім – подяку і завершується.
В англомовній літературі такі розгалужені умовні оператори
зазвичай називають chained conditionals, тобто ланцюжками умов.

1.5.5. Порожні блоки

У процесі роботи над програмою слід намагатися після кож-


ної зміни мати працюючу програму. Однак припустимо, що ви
запланували використання умовного оператора з кількома умо-
вами, встигнувши написати тільки один із його блоків. Постає
запитання: як налагодити програму, якщо вона не виконується
через синтаксичну помилку? Наприклад, розглянемо програму:
choice = raw_input('Enter your choice, please:')
if choice == "1":
function1_1()
finction1_2()
elif choice == "2":
elif choice == "3":
elif choice == "4":
else:
print "Invalid choice!"
27
Блоки для випадків, коли значення змінної choice дорівнює
"2", "3" або "4", ще не написані, тому програма не виконується
через помилку Expected an indented block. Уникнути цього мож-
на за допомогою ключового слова pass, яке можна вставити на
місце відсутнього блоку:
choice = raw_input('Enter your choice, please:')
if choice == "1":
function1_1()
finction1_2()
elif choice == "2":
pass
elif choice == "3":
pass
elif choice == "4":
pass
else:
print "Invalid choice!"
Це ключове слово може використовуватися також як тіло
функції:
def f1():
pass
Така функція нічого не робить – потік виконання, "заглянув-
ши" в неї, одразу переходить до виконання наступної за її ви-
кликом інструкції.

1.5.6. Вкладені умовні оператори


(Nested conditionals)

У блоках умовного оператора теж можуть зустрічатися умо-


вні оператори, адже вони, з погляду Python, нічим не відрізня-
ються від інших інструкцій. Наприклад:
if x == y:
print x, "and", y, "are equal"
else:
if x < y:
28
print x, "is less than", y
else:
print x, "is greater than", y
Перший логічний оператор містить дві гілки, друга з яких
теж розгалужується. Однак не варто занадто зловживати вкла-
деними логічними операторами, тому що це може призвести до
погіршення читабельності програми. Тут на допомогу нам при-
ходять логічні операції. Розглянемо приклад програми:
if x < 1:
pass
else:
if x < 10:
print "x is between 1 and 10"
Цей код працює так: якщо x < 1, то нічого не відбувається,
тому що потік виконання, зустрівши інструкцію pass, перейде до
рядка, наступного після умовного оператора; в іншому випадку
виконається перевірка логічного виразу x < 10. Якщо він істин-
ний, то інтерпретатор виведе рядок "x is between 1 and 10". Мо-
жна написати семантично ідентичний умовний оператор, але
набагато коротше:
if not x < 1 and x < 10:
print "x is between 1 and 10"
Чи можна зробити його ще простіше? Хотілося б позбутися
логічного оператора заперечення у виразі, що перевіряється
оператором if.
Вираз not x < 1 істинний, якщо x не менше одиниці або x бі-
льше чи дорівнює одиниці – x > = 1:
if x >= 1 and x < 10:
print "x is between 1 and 10"
Однак не будемо на цьому зупинятися. У математиці є коро-
тший спосіб запису умови приналежності x деякому відрізку
числової прямої: 1 ≤x <10. Адаптуємо її під оператори порів-
няння, які є в мові Python:
>>> x = 3
>>> if 1 <= x < 10:

29
... print "x is between 1 and 10"
...
x is between 1 and 10
>>>
Вправи
1. Шахова дошка. Програма визначає, чи пофарбовані кліти-
ни на шаховій дошці в один колір, за їх координатами х1 та у1,
х2 та у2, що задають номер стовпця і номер рядка спочатку для
першої клітини, потім для другої.
На вході: чотири цілих числа (1 ≤ х1, у1, х2, у2 ≤ 8).
На виході: одно з двох рядкових значень: 'YES' або 'NO'.
Введіть координати 1 і 2 клітини: 1 1 2 6 YES
Введіть координати 1 і 2 клітини: 1 3 8 7 NO
2. Шахова тура. Програма визначає, чи може тура потрапити
з першої клітини на другу одним ходом за координатами х1 та
у1, х2 та у2, що задають номер стовпчика і номер рядка спочат-
ку для першої клітини, потім для другої. Шахова тура ходить по
горизонталі або вертикалі.
На вході: чотири цілих числа (1≤ х1, у1, х2, у2 ≤ 8).
На виході: одно з двох рядкових значень: 'YES' або 'NO'.
Введіть координати 1 і 2 клітини: 1 1 1 8 YES
Введіть координати 1 і 2 клітини: 4 4 5 5 NO
3. Шаховий слон. Програма визначає, чи може слон потрапи-
ти з першої клітини на другу одним ходом за координатами х1
та у1, х2 та у2, що задають номер стовпця і номер рядка спочат-
ку для першої клітини, потім для другої. Шаховий слон ходить
по діагоналі.
На вході: чотири цілих числа (1≤ х1, у1, х2, у2 ≤ 8).
На виході: одно з двох рядкових значень: 'YES' або 'NO'.
Введіть координати 1 і 2 клітини: 1 1 1 8 NO
Введіть координати 1 і 2 клітини: 4 4 5 5 YES
4. Шаховий кінь. Програма визначає, чи може кінь потрапити
з першої клітини на другу одним ходом за координатами х1 та
у1, х2 та у2, що задають номер стовпчика і номер рядка спочат-
30
ку для першої клітини, потім для другої. Шаховий кінь ходить
літерою "Г" – на дві клітини з вертикалі в будь-якому напрямку і
на одну – по горизонталі, або навпаки.
На вході: чотири цілих числа (1≤ х1, у1, х2, у2 ≤ 8).
На виході: одно з двох рядкових значень: 'YES' або 'NO'.
Введіть координати 1 і 2 клітини: 2 4 3 2 YES
Введіть координати 1 і 2 клітини: 2 8 3 7 NO

1.6. Цикли. Оператор циклу while


Кожен циклічний оператор має тіло циклу – якийсь блок ко-
ду, який інтерпретатор буде повторювати, поки умова повто-
рення циклу залишатиметься істинною.
У мові Python оператор циклу має такий вигляд:
while УМОВА_ПОВТОРЕННЯ_ЦИКЛУ:
ТІЛО_ЦИКЛУ
Дуже схоже на оператор умови, тільки тут використовується
інше ключове слово: while (англ. "Поки"). Де його можна вико-
ристовувати? Перше, що спадає на думку: повтор введення да-
них користувачем, поки не буде отримане коректне значення:
correct_choice = False
while not correct_choice:
choice = raw_input("Enter your choice, please (1 or 2):")
if choice == "1" or choice == "2":
correct_choice = True
else:
print "Invalid choice! Try again, please."
print "Thank you."
Перед початком циклу ми визначили логічну змінну
correct_choice, присвоївши їй значення False. Потім оператор циклу
перевіряє умову not correct_choice: заперечення False – істина. То-
му починається виконання тіла циклу: виводиться запрошення
"Enter your choice, please (1 or 2):" і очікується введення користува-
ча. Після натискання клавіші [Enter] введене значення порівнюєть-
ся з рядками "1" та "2"; якщо воно дорівнює одному з рядків, то
31
змінній correct_choice присвоюється значення True. В іншому ви-
падку програма виводить повідомлення "Invalid choice! Try again,
please.". Потім оператор циклу знову перевіряє умову; якщо вона,
як і раніше, істинна, то тіло циклу повторюється знову, інакше
потік виконання переходить до наступного оператора та інтерпре-
татор виводить рядок "Thank you.". Отже, усе досить просто.
Примітка для програмістів мовою C / C++. Пам'ятайте, що в
циклі while може бути блок else.

1.6.1. Лічильники

Ще один варіант використання оператора циклу – обчислен-


ня формул зі змінним параметром. Розглянемо такий код:
n = input("Input n, please:")
sum = 0
i=1
while i <= n:
sum = sum + i**3 #
Те саме можна записати коротше:
sum +=i**3
i=i+1 #
Аналогічно
i += 1
print "sum = ", sum
Розберемося, як працює ця програма. Спочатку користувач
вводить n – граничне значення i. Потім виконується ініціалізація
змінних sum (у ній буде зберігатися результат, початкове значен-
ня 0) і лічильника i (за умовою початкове значення 1). Далі почи-
нається цикл, який виконується, поки i <= n. У тілі циклу в змінну
sum записується сума її попереднього значення і значення змінної
i, піднесене до куба; лічильнику i присвоюється наступне значен-
ня. Після завершення циклу виводиться значення sum.
Наступне значення лічильника отримують додаванням до йо-
го поточного значення кроку циклу (у даному випадку крок ци-
клу дорівнює 1). Крок циклу за необхідності може бути від'єм-
ним і навіть дробовим. Крім того, він може змінюватися на кож-
ній ітерації (тобто при кожному повторенні тіла циклу).
32
Тепер спробуємо оптимізувати нашу програму. Отже, на ко-
жній ітерації в тілі циклу ми обчислюємо наступне значення
лічильника. Однак на останньому кроці ми вирахували його
зайвий раз – його нове значення вже використовуватися не буде.
Ця операція не вимагає якихось особливих ресурсів, але звичку
звертати увагу на подібні речі слід виробляти, тому що згодом
ви, напевно, матимете справу зі складнішими операціями. Як
уникнути виконання зайвого обчислення? Що якщо обчислюва-
ти значення i до обчислення формули? Давайте спробуємо:
n = input("Input n, please:")
sum = i = 0
while i <= n:
i += 1
sum += i**3
print "sum = ", sum
Зверніть увагу на запис вигляду sum = i = 0. Він працює ана-
логічно простому присвоюванню значення змінної, але в даному
випадку обидві змінні отримують однакове початкове значення.
Отже, тепер початкове значення i дорівнює 0, але на першо-
му етапі ми відразу ж додаємо до нього одиницю. У результаті
програма видає ті самі результати, що й попередня її модифіка-
ція. Чи змінилася кількість операцій?
Ні, не змінилася, але, мабуть, має трохи витонченіший ви-
гляд: після завершення циклу i=n. Це може допомогти уникнути
плутанини, якщо i буде використовуватися далі в програмі.

1.6.2. Нескінченні цикли

Іноді можна зіткнулися із проблемою нескінченного повто-


рення блоку коду, що виникає, коли умови повернення не можна
досягти, наприклад через семантичні помилки в програмі. Цик-
ли можуть виконуватися нескінченно, найчастіше через семан-
тичні помилки. Найпростіший приклад:
i=0
while i < 10:
print i
33
Цикл буде виконуватися нескінченно, тому що умова i < 10
завжди буде істинною, адже значення змінної i не змінюється.
Така програма буде виводити нескінченну послідовність нулів.
Немає обмеження на кількість повторень тіла циклу, тому про-
грама з нескінченним циклом буде працювати безперервно, по-
ки ми не виконаємо аварійне припинення натисканням комбіна-
ції клавіш [Ctrl + C], не зупинимо процес засобами операційної
системи, не будуть вичерпані доступні ресурси комп'ютера (на-
приклад, може бути досягнута максимально допустима кількість
відкритих файлів) або не виникне виняток. У таких ситуаціях
програмісти кажуть, що програма зациклилася.
Знайти зациклення в програмі іноді буває не просто, адже в
реальних програмах зазвичай використовується безліч циклів,
кожен з яких потенційно може виконуватися нескінченно.
Проте відсутність обмеження на кількість повторів тіла цик-
лу дає нам нові можливості. Багато програм має цикл, у тілі яко-
го відстежуються й обробляються дії користувачів або запити з
інших програмних і автоматизованих систем. Такі програми
можуть працювати без перебоїв тривалий термін, іноді роками!

1.6.3. Альтернативна гілка циклу while

Мова Python має багато цікавих і корисних особливостей,


однією з яких є розширений варіант оператора циклу:
while УМОВА_ПОВТОРЕННЯ_ЦИКЛУ:
ТІЛО_ЦИКЛУ
else:
АЛЬТЕРНАТИВНА_ГІЛКА_ЦИКЛУ
Поки виконується умова повторення тіла циклу, оператор
while працює так само, як і у звичайному варіанті, але як тільки
умова повторення перестає виконуватися, потік виконання на-
правляється по альтернативній гілці else – так само, як в умов-
ному операторі if, він виконується лише один раз.
>>> i = 0
>>> while i < 3:
print i
34
i += 1
else:
print "end of loop"
0
1
2
end of loop
>>>

1.6.4. Табулювання функцій

За допомогою оператора циклу while зручно будувати таблиці


значень різних функцій. По суті такі таблиці є списком значень
функції за різних значень її параметра. Розглянемо таку програму:
import math
x = 1.0
while x < 10.0:
print x, "\t", math.log(x)
x += 1.0
Результат її роботи матиме вигляд:
1.0 0.0
2.0 0.69314718056
3.0 1.09861228867
4.0 1.38629436112
5.0 1.60943791243
6.0 1.79175946923
7.0 1.94591014906
8.0 2.07944154168
9.0 2.19722457734
Рядок "\ t" позначає знак табуляції. Завдяки йому значення
шикуються у два стовпчики.
Розберемо, як ця програма працює. Параметр x змінюється
від 1.0 із кроком 1.0, поки він менше 10.0. У тілі циклу виво-
диться поточне значення параметра x, потім знак табуляції і
результат обчислення функції math.log (x), тобто натуральний
логарифм від x (loge x = ln (x)). За необхідності обчислення ло-
гарифма за основою 2 ми можемо скористатися формулою
35
log2 x = ln x / ln 2.
Наша програма матиме вигляд:
import math
x = 1.0
while x < 10.0:
print x, "\t", math.log(x)/math.log(2)
x += 1.0
Результат буде таким:
1.0 0.0
2.0 1.0
3.0 1.58496250072
4.0 2.0
5.0 2.32192809489
6.0 2.58496250072
7.0 2.80735492206
8.0 3.0
9.0 3.16992500144
Як бачите, 1, 2, 4 та 8 є степенями 2. Модифікуємо нашу про-
граму, щоб знайти інші степені 2:
import math
x = 1.0
while x < 100.0:
print x, "\t", math.log(x)/math.log(2)
x *= 2.0
Таким чином, ми отримали:
1.0 0.0
2.0 1.0
4.0 2.0
8.0 3.0
16.0 4.0
32.0 5.0
64.0 6.0
Завдяки символу табуляції позиція другої колонки не зале-
жить від ширини першої – це добре видно на останніх трьох
значеннях.

36
1.6.5. Вкладені оператори циклу
і двовимірні таблиці

Як ви вже, напевно, здогадалися, подібно логічним операто-


рам цикли можуть бути вкладені один в один. При цьому цикли
можуть використовувати різні змінні-лічильники.
Найпростіше застосування вкладених операторів циклу – по-
будова двовимірних таблиць, наприклад:
i=1
while i <= 10:
j=1
while j <= 10:
print i * j, "\t",
j += 1
print
i += 1
Розберемось із тим, як працює ця програма. Цикл, що переві-
ряє умову i<=10, відповідає за повторення рядків таблиці.
У його тілі виконується другий цикл, який виводить добуток i і j
десять разів поспіль, розділяючи знаками табуляції отримані
результати. Кома, що завершує вираз, забороняє інтерпретатору
переводити курсор на новий рядок. Також зверніть увагу на
команду print без параметра, наступну після внутрішнього цик-
лу. Вона здійснює переведення на новий рядок.
У результаті виконання програми отримаємо таку таблицю:
1 2 3 4 5 6 7 8 9 10
2 4 6 8 10 12 14 16 18 20
3 6 9 12 15 18 21 24 27 30
4 8 12 16 20 24 28 32 36 40
5 10 15 20 25 30 35 40 45 50
6 12 18 24 30 36 42 48 54 60
7 14 21 28 35 42 49 56 63 70
8 16 24 32 40 48 56 64 72 80
9 18 27 36 45 54 63 72 81 90
10 20 30 40 50 60 70 80 90 100
Перший стовпець і перший рядок містять числа від 1 до 10.
На перетині i-го рядка та j-го стовпця міститься добуток i і j. Ми
отримали таблицю множення.
37
Якщо додамо ще один цикл, то отримаємо тривимірну таб-
лицю додатків трьох чисел. Однак виводити її на екран буде
незручно, хіба що "пошарово". Таким чином, вкладені цикли
дозволяють будувати таблиці будь-якої розмірності, що дуже
часто застосовується у складних наукових розрахунках, а також
у тривимірній графіці.

1.6.6. Анонімні функції (функція lambda)

Python не належить до функціональних мов програмування,


але реалізує окремі можливості функціонального стилю, напри-
клад, дозволяє створювати лямбда-вирази.
Розглянемо такий приклад. Для початку визначимо функцію
edit_story ():
>>> def edit_story(words, func):
for word in words:
print(func(word))
Тепер необхідно визначити список і функцію, які будуть пе-
редаватися як параметри в edit_story ():
>>> s = ['aaaaa', 'bbbbbb', 'cccccc'] # створення списку
>>> def f(word):
return word.capitalize() + '!'
>>> edit_story(s, f) # викликали функцію й передали f()
Aaaaa!
Bbbbbb!
Cccccc!
>>>
Замість створення окремої функції f () напишемо лямбда-
функцію всередині виклику edit_story ():
>>> edit_story(s, lambda word: word.capitalize() + '!')
Aaaaa!
Bbbbbb!
Cccccc!
>>>

38
1.6.7. Функція генератора
У попередніх прикладах ми вже зустрічалися з функцією
range (), яка генерує послідовність цілих чисел без необхідності
створення всієї послідовності та її зберігання в пам'яті.
Напишемо власну функцію-генератор, яка відрізняється тим,
що замість return використовує інструкцію yield:
>>> def my_range(first=0, last=10, step=1):
number = first
while number < last:
yield number
number += step
>>> my_range
<function my_range at 0x02DF6228>
>>> ranger = my_range(1, 5)
Бачимо, що функція my_range () повернула об'єкт генератора:
>>> ranger
<generator object my_range at 0x02E00450>
>>> for i in ranger:
print(i)
1
2
3
4

Вправи
1. Заробітна платня. Працівник заробляє X гривень за 38 год
роботи. Йому платять у 1,5 раза більше за кожну годину понад
38 год. Яку суму він отримає, якщо відпрацює A годин?
На вході: два цілих числа, розділених пробілом (Х А).
На виході: ціле число.
38 1 38 грн
38 40 41 грн
2. Банківські відсотки. Внесок у банк становить x грн. Щорі-
чно він збільшується на p відсотків, після чого дрібна частина
(копійки) відкидається. Визначте, за скільки років внесок стано-
витиме не менше y грн.
39
На вході: три натуральних числа: x, p, y, розділених пробілом.
На виході: ціле число.
100 10 200 8 років
112 100 років
100 1 200 70 років
400 7 1046 15 років

3. Добуток. Напишіть програму, яка отримує два цілих числа


і знаходить їхній добуток, не використовуючи операцію мно-
ження. Урахуйте, що числа можуть бути від'ємними.
На вході: два цілих числа, розділених пробілом (Х А).
На виході: ціле число.
10 20 200
60 60 3600
-1 1000 -1000
10 -11 -110

4. Значення функції. Вивести на екран таблицю значень фун-


кції. Виведення значень виконується у два стовпчики: перший –
значення аргументу, другий – значення функції при зміні аргу-
менту від значення a до b із кроком d.

πx
f ( x) = lg(3) + x 5sin( ).
3
5. Знайти всі числа за умовою. Знайдіть усі п'ятизначні числа,
які при діленні на 133 дають у залишку 125, а при діленні на 134
дають у залишку 111.
19809 37631 55453 73275 91097

6. Число Армстронга. Натуральне число називається числом


Армстронга, якщо сума цифр числа, піднесених до N-го степеня
(де N – кількість цифр у числі), дорівнює самому числу. Напри-
клад: 153 = 13 + 53 + 33.
Знайдіть усі тризначні числа Армстронга.

40
1.7. Цикли. Оператор циклу for
Оператор for..in також є оператором циклу, який здійснює
ітерацію за послідовністю об'єктів, тобто проходить через кожен
елемент у послідовності (упорядкований набір елементів).
for i in range(1, 5):
print(i)
else:
print('Цикл for закончен')
Виведення:
1
2
3
4
Цикл for закінчено.
Як це працює. У програмі ми виводимо на екран послідов-
ність чисел. Генеруємо цю послідовність, використовуючи вбу-
довану функцію range.
Задаємо два числа, і range повертає послідовність чисел від
першого числа до другого. Наприклад, range (1,5) дає послідов-
ність [1, 2, 3, 4]. За умовчанням range набуває значення кроку,
що дорівнює 1. Якщо ми поставимо також третє число range, то
воно буде кроком. Наприклад, range (1,5,2) дасть [1,3]. Пам'я-
тайте, інтервал простягається тільки до другого числа, тобто не
включає його в себе.
Зверніть увагу, що range () генерує послідовність чисел, але
тільки по одному числу за раз – коли оператор for запитує на-
ступний елемент. Щоб побачити всю послідовність чисел відра-
зу, використовуйте list (range ()).
Потім цикл for здійснює ітерацію з цього діапазону – for i in
range (1,5) еквівалентно for i in [1, 2, 3, 4], що нагадує присвою-
вання змінній i по одному числу (або об'єкту) за раз, виконуючи
блок команд для кожного значення i. У даному випадку в блоці
команд ми просто виводимо значення на екран.
Пам'ятайте, що блок else не обов'язковий. Якщо він присут-
ній, то завжди виконується один раз після закінчення циклу for,
якщо тільки не вказано оператор break.
41
Цикл for..in працює для будь-якої послідовності. У нашому
випадку – це список чисел, генерований вбудованою функцією
range, але в загальному випадку можна використовувати будь-
яку послідовність будь-яких об'єктів!
Примітка для програмістів мовами C/C++/Java/C#: цикл for у
Python радикально відрізняється від циклу for у C/C++. Програ-
місти мовою C # помітять, що цикл for у Python схожий на цикл
foreach у C #. Програмістам мовою Java це може нагадати конс-
трукцію for (int i: IntArray).
Якщо в C/C++ записати for (int i = 0; i <5; i ++), то в Python
цьому відповідатиме вираз for i in range (0,5). Отже, у Python
цикл for простіший, виразніший і менше схильний до помилок.

1.7.1. Оператор break

Оператор break служить для переривання циклу, тобто зупи-


нення виконання команд, навіть якщо умова виконання циклу
ще не набула значення False або послідовність елементів не за-
кінчилася.
Важливо зазначити, що якщо цикли for або while перервати
оператором break, то відповідні до них блоки else виконуватися
не будуть:
while True:
s = input('Введіть що-небудь: ')
if s == 'вихід':
break
print('Довжина рядка: ', len(s))
print('Завершення')
Виведення:
Введіть що-небудь : Програмувати весело.
Довжина рядка: 20
Введіть що-небудь : Якщо робота нудна,
Довжина рядка: 19
Введіть що-небудь : Щоб надати їй веселий тон –
Довжина рядка: 30
Введіть що-небудь : використовуй Python!
42
Довжина рядка: 23
Введіть що-небудь : вихід
Завершення
Як це працює. У цій програмі ми багато разів зчитуємо вве-
дення користувача і виводимо на екран довжину кожного введе-
ного рядка. Для зупинки програми вводимо спеціальну умову,
що перевіряє, чи збігається введення користувача з рядком 'ви-
хід'. Зупиняємо програму перериванням циклу оператором break
і досягаємо її кінця. Довжина введеного рядка може бути знай-
дена за допомогою вбудованої функції len.

1.7.2. Оператор continue

Оператор continue використовується для вказівки Python, що


необхідно пропустити решту команд у поточному блоці циклу і
продовжити з наступної ітерації циклу:
while True:
s = input (Введіть що-небудь : ')
if s == 'вихід':
break
if len(s) < 3:
print ('Замало')
continue
print ('Уведений рядок достатньої довжини')
# Різні інші дії тут...
Виведення:
Введіть що-небудь : a
Замало
Введіть що-небудь : 12
Замало
Введіть що-небудь : абв
Уведений рядок достатньої довжини
Введіть що-небудь : вихід
Як це працює. У цій програмі ми запитуємо введення з боку
користувача, але обробляємо введений рядок тільки якщо він
має довжину хоча б у 3 символи.
43
Отже, ми використовуємо вбудовану функцію len для отри-
мання довжини рядка, і якщо довжина менше 3, то пропускаємо
інші дії в блоці за допомогою оператора continue. В іншому ви-
падку всі інші команди в циклі виконуються, здійснюючи будь-
які потрібні маніпуляції.

Вправи
1. Ряд. Напишіть програму, що виводить на екран усі числа
від А до В включно за зростанням, якщо A <B, або за спаданням
– в іншому випадку.
На вході: у першому рядку вводиться А, у другому – B.
На виході: послідовність цілих чисел, розділених пробілом.
Введіть А: 1
1 2 3 4 5 6 7 8 9 10
Введіть В: 10
Введіть А: 10
10 9 8 7 6 5 4 3 2 1
Введіть В: 1
2. Непарні числа. Напишіть програму, що виводить на екран
усі непарні числа від А до В включно (А≤В).
На вході: два цілих числа, розділених пробілом.
На виході: послідовність цілих чисел, розділених пробілом.
Введіть два числа: 1 7 1357
Введіть два числа: -18 -29 -19 -21 -23 -25 -27 -29
3. Драбинка. За даним натуральним n ≤ 9 виведіть драбинку
із n сходинок, де i-та сходинка складається із чисел від 1 до i без
пробілів.
На вході: ціле число n.
На виході: цілі числа.
1
Введіть n: 3 12
123
Введіть n: 1 1
4. Таблиця множення. Скласти програму, що виводить таб-
лицю множення із заданим першим і другим числом від 1 до 9.
На вході: два цілих числа, розділених пробілом.

44
На виході: таблиця множення.
1х1=1|1х2=2|1х3=3
Введіть два числа: 1 3 2х1=2|2х2=4|2х3=6
3x1=3|3x2=6|3x3=9
Введіть два числа: 4 4 4 x 1 = 4 | 4 x 2 = 8 | 4 x 3 = 12 | 4 x 4 = 16
5. Кількість нулів у числі. Напишіть програму, яка обчислює
кількість нулів у заданому числі (N < 1Е11).
На вході: ціле число N.
На виході: ціле число.
Введіть n: 103 235 045 207 3
Введіть n: 10 984 1

1.8. Визначення та використання


функцій і модулів

1.8.1. Функції
Функції – це багаторазово використовувані фрагменти про-
грами. Вони дозволяють дати ім'я певному блоку команд із тим,
щоб надалі запускати цей блок за вказаним іменем у будь-якому
місці програми скільки завгодно разів. Це називається викликом
функції. Результат виклику функції можна присвоїти змінній,
використовувати його як операнди математичних виразів, тобто
складати складніші вирази.
Розглянемо кілька популярних математичних функцій
мови Python.
pow (x, y) повертає значення x у степені y. Еквівалентно за-
пису x ** y.
>>> pow(4,5)
1024
>>>
round (number) повертає число з плаваючою точкою, округ-
лене до 0 цифр після коми (за умовчанням). Може бути викли-
кана з двома аргументами:
round (number [, ndigits]), де ndigits – кількість знаків після коми.
45
>>> round(4.56666)
5
>>> round(4.56666, 3)
4.567
>>>
Крім створення складних математичних виразів, Python до-
зволяє передавати результати виконання функцій як аргументів
інших функцій без використання додаткових змінних (рис. 1.3):

Рис. 1.3

На рисунку зображений приклад виклику і порядок обчис-


лення виразів. Тут на місці числових об'єктів (-2, 4.3) можуть
бути виклики функцій або їхні комбінації, тому вони теж обчис-
люються.
Часто при написанні програм необхідно перетворити об'єкти
різних типів. Тому розглянемо функції для їх перетворення.
int () повертає цілочисловий об'єкт, побудований із числа або
рядка, або 0, якщо аргументи не передані.
float () повертає число з плаваючою точкою, побудоване з чи-
сла або рядка.
Розглянемо приклади:
>>> int(5.6)
5
>>> int()
0
>>> float(5)
5.0
>>> float()
0.0
>>>

46
Функції визначаються за допомогою зарезервованого слова
def. Після цього слова вказується ім'я функції, потім – пара ду-
жок, у яких можна вказати імена деяких змінних, і двокрапка в
кінці рядка. Далі йде блок команд, що складають функцію:
def sayHello():
print('Привіт, Світ!') # блок, що належить функції
# Кінець функції
sayHello() # виклик функції
sayHello() # ще один виклик функції
Виведення:
Привіт, Світ!
Привіт, Світ!
Як це працює. Ми визначили функцію з ім'ям sayHello, вико-
ристовуючи описаний вище синтаксис. Ця функція не приймає
параметрів, тому в дужках не оголошені будь-які змінні. Пара-
метри функції – це такі вхідні дані, які можна передати функції,
щоб отримати відповідний до них результат.
Зверніть увагу, що можна викликати ту саму функцію багато ра-
зів, а значить, немає необхідності писати той самий код знову і знову.

1.8.2. Параметри функцій

Функції можуть приймати параметри, тобто деякі значення,


що передаються функції для того, щоб вона щось зробила з ни-
ми. Ці параметри схожі на змінні, за винятком того, що значення
змінних указуються при виклику функції, а під час роботи фун-
кції їм уже присвоєні значення.
Параметри вказуються в дужках при оголошенні функції та ро-
зділяються комами. Аналогічно ми передаємо значення, коли ви-
кликаємо функцію. Зверніть увагу на термінологію: імена, зазначе-
ні в оголошенні функції, називаються параметрами, тоді як зна-
чення, які ви передаєте у функцію при її виклику, – аргументами.
def printMax(a, b):
if a > b:
print(a, 'максимально')

47
elif a == b:
print(a, 'дорівнює', b)
else:
print(b, 'максимально')
printMax(3, 4) # пряма передача значень
x=5
y=7
printMax(x, y) # передача змінних як аргументів
Виведення:
4 максимально
7 максимально
Як це працює. Тут ми визначили функцію з ім'ям printMax,
яка використовує два параметри з іменами a та b. Ми знаходимо
найбільше число із застосуванням простого оператора if..else і
виводимо це число.
При першому виклику функції printMax ми безпосередньо пе-
редаємо числа як аргументи. У другому випадку викликаємо фу-
нкцію зі змінними як аргументами. printMax (x, y) призначає зна-
чення аргументу x параметру a, а значення аргументу y – параме-
тру b. В обох випадках функція printMax працює однаково.

1.8.3. Локальні змінні

При оголошенні змінних усередині визначення функції вони


жодним чином не зв'язані з іншими змінними з таким самим
ім'ям за межами функції, тобто імена змінних є локальними у
функції. Це називається областю видимості змінної. Область
видимості всіх змінних обмежена блоком, у якому вони оголо-
шені, починаючи з точки оголошення імені.
x = 50

def func(x):
print('x дорівнює', x)
x=2
print('Заміна локального x на', x)
48

func(x)
print('x як і раніше ', x)
Виведення:
x дорівнює 50
Заміна локального x на 2
x як і раніше 50
Як це працює. При першому виведенні значення, призначеного
імені x, у першому рядку функція Python використовує значення па-
раметра, оголошеного в основному блоці, вище визначення функції.
Далі ми призначаємо x значення 2. Ім'я x локальне для нашої
функції. Тому, коли ми замінюємо значення x у функції, x, ого-
лошений в основному блоці, залишається незачепленим.
Останнім викликом функції print ми виводимо значення x,
указане в основному блоці, підтверджуючи, таким чином, що
воно не змінилося при локальному присвоєнні значення в рані-
ше викликаній функції.

1.8.4. Зарезервоване слово "global"

Щоб присвоїти деяке значення змінній, визначеній на вищо-


му рівні програми (тобто не в якій-небудь області видимості),
необхідно вказати Python, що її ім'я не локальне, а глобальне
(global). Зробимо це за допомогою зарезервованого слова global.
Без застосування зарезервованого слова global неможливо при-
своїти значення змінній, визначеній за межами функції.
Можна використовувати вже існуючі значення змінних, ви-
значених за межами функції (за умови, що всередині функції не
було оголошено змінної з таким самим ім'ям). Однак цього кра-
ще уникати, оскільки людині, що читає текст програми, буде
незрозуміло, де міститься оголошення змінної. Використання
зарезервованого слова global досить ясно показує, що змінна
оголошена в самому зовнішньому блоці.
x = 50

49
def func():
global x

print('x дорівнює', x)
x=2
print('Замінюємо глобальне значення x на', x)
func()
print('Значення x становить', x)
Виведення:
x дорівнює 50
Замінюємо глобальне значення x на 2
Значення x становить 2
Як це працює. Зарезервоване слово global використовується
для того, щоб оголосити, що x – глобальна змінна, тобто коли ми
присвоюємо значення імені x усередині функції, ця зміна відобра-
зиться на значенні змінної x в основному блоці програми.
Використовуючи одне зарезервоване слово global, можна
оголосити відразу кілька змінних:
global x, y, z

1.8.5. Зарезервоване слово "nonlocal"

Є ще один тип області видимості, що називається нелокаль-


ною (nonlocal) областю видимості та є чимось середнім між лока-
льною та глобальною областями. Нелокальні області видимості
зустрічаються, коли визначаються функції всередині функцій.
Оскільки в Python програма є здійсненним кодом, то можна
визначати функції де завгодно. Розглянемо приклад:
def func_outer():
x=2
print('x дорівнює', x)
def func_inner():
nonlocal x
x=5
50
func_inner()
print('Локальне x змінилось на', x)
func_outer()
Виведення:
x дорівнює 2
Локальне x змінилось на 5
Як це працює. Коли ми перебуваємо всередині func_inner,
змінна x, що визначена в першому рядку func_outer, не містить-
ся ні в локальній області видимості (визначення змінної не вхо-
дить у блок func_inner), ні в глобальному контексті (і не в осно-
вному блоці програми). Ми оголошуємо, що хочемо використо-
вувати саме цю змінну x, таким чином: nonlocal x.
Спробуйте замінити "nonlocal x" на "global x", а потім вида-
лити це зарезервоване слово, і поспостерігайте за різницею між
двома випадками.

1.8.6. Значення аргументів за умовчанням

Найчастіше деякі параметри функцій можуть бути необов'яз-


ковими. Для них використовуються певні задані значення за
умовчанням, якщо користувач не вкаже власних. Цього можна
досягти за допомогою значень аргументів за умовчанням. Їх
можна вказати, додавши до імені параметра у визначенні функ-
ції оператор присвоювання (=) з наступним значенням.
Зверніть увагу, що значення за умовчанням має бути конста-
нтою, а точніше кажучи, воно має бути незмінним.
def say(message, times = 1):
print(message * times)
say('Привіт')
say('Світ', 5)
Виведення:
Привіт
СвітСвітСвітСвітСвіт

51
Як це працює. Функція під ім'ям say використовується для
виведення на екран рядка вказану кількість разів. Якщо ми не
вказуємо значення, то за умовчанням рядок виводиться один
раз. Ми досягаємо цього зазначенням значення аргументу за
умовчанням, що дорівнює 1 для параметра times.
При першому виклику say ми вказуємо тільки рядок, і функція
виводить її один раз; при другому виклику вказуємо також аргумент
5, позначаючи, таким чином, що хочемо повторити фразу 5 разів.

1.8.7. Ключові аргументи

Якщо є деяка функція з великою кількістю параметрів і при її


виклику потрібно вказати тільки деякі з них, то значення цих пара-
метрів можуть задаватися за їхніми іменами. Такі параметри нази-
ваються ключовими. У такому випадку для передачі аргументів
функції використовується ім'я (ключ) замість позиції (як було досі).
Є дві переваги такого підходу: по-перше, використання фун-
кції стає легшим, оскільки немає необхідності відстежувати
порядок аргументів; по-друге, можна задавати значення тільки
деяким обраним аргументам, за умови, що інші параметри ма-
ють значення аргументу за умовчанням.
def func(a, b=5, c=10):
print('a дорівнює', a, ', b дорівнює ', b, ', а c дорівнює ', c)
func(3, 7)
func(25, c=24)
func(c=50, a=100)
Виведення:
a дорівнює 3, b дорівнює 7, а c дорівнює 10
a дорівнює 25, b дорівнює 5, а c дорівнює 24
a дорівнює 100, b дорівнює 5, а c дорівнює 50
Як це працює. Функція з ім'ям func має один параметр без
значення за умовчанням, за яким ідуть два параметри зі значен-
нями за умовчанням.
При першому виклику func (3, 7) параметр a отримує значен-
ня 3, параметр b – значення 7, а c отримує своє значення за умо-
вчанням, що дорівнює 10.
52
При другому виклику func (25, c = 24) змінна a отримує зна-
чення 25 унаслідок позиції аргументу. Після цього параметр c
отримує значення 24 за іменем, тобто як ключовий параметр.
Змінна b отримує значення за умовчанням, що дорівнює 5.
При третьому зверненні до func (c = 50, a = 100) ми викорис-
товуємо ключові аргументи для всіх зазначених значень. Звер-
ніть увагу на те, що ми вказуємо значення для параметра c перед
значенням для a, навіть незважаючи на те, що у визначенні фун-
кції параметр a вказано раніше c.

1.8.8. Змінна кількість параметрів

Іноді буває потрібно визначити функцію, здатну приймати


будь-яку кількість параметрів. Цього можна досягти за допомо-
гою зірочок. Коли ми оголошуємо параметр із зірочкою (напри-
клад * param), усі позиційні аргументи, починаючи з цієї позиції
й до кінця, будуть зібрані в кортеж під ім'ям param.
Аналогічно, коли ми оголошуємо параметри з двома зірочка-
ми (** param), усі ключові аргументи, починаючи з цієї позиції й
до кінця, будуть зібрані у словник під ім'ям param.
def total (initial=5, *numbers, **keywords):
count = initial
for number in numbers:
count += number
for key in keywords:
count += keywords[key]
return count
# 1, 2, 3 – позиційні аргументи, vegetables та fruits – ключові
аргументи
print(total(10, 1, 2, 3, vegetables=50, fruits=100))
Виведення:
166
Якщо деякі ключові параметри мають бути доступні тільки за
ключем, а не як позиційні аргументи, то їх можна оголосити після
параметра із зірочкою. Оголошення параметрів після параметра із
53
зірочкою дає тільки ключові аргументи. Якщо для таких аргумен-
тів не вказане значення за умовчанням і воно не передане при
виклику, то звернення до функції викликає помилку:
def total (initial=5, *numbers, extra_number):
count = initial
for number in numbers:
count += number
count += extra_number
print (count)
total (10, 1, 2, 3, extra_number=50)
total (10, 1, 2, 3)
# Викликає помилку, оскільки ми не вказали значення
# Аргументу за умовчанням для 'extra_number'
Виведення:
66
Traceback (most recent call last):
File "C:/Python35-32/test.py", line 9, in <module>
total (10, 1, 2, 3)
TypeError: total() missing 1 required keyword-only argument:
'extra_number'
>>>

1.8.9. Декоратори

Декоратор у Python – функція, яка приймає одну функцію як


аргумент і повертає іншу функцію. Використовується, коли не-
обхідно модифікувати існуючу функцію, не змінюючи її вихід-
ний код.
Визначимо функцію-декоратор doc_it ():
>>> def doc_it(func):
def new_func(*args, **kwargs):
print("Ім'я функції: ", func.__name__)
print("Позиційні аргументи: ", args)
print("Ключові аргументи: ", kwargs)
result = func(*args, **kwargs)

54
print("Результат: ", result)
return result
return new_func
>>> def add_ints(a, b):
return a + b
>>> add_ints(3, 6)
9
>>> # ручне присвоювання декоратора:
>>> new_add_ints = doc_it(add_ints)
У результаті отримали нову функцію, яка містить додаткові
вирази, що додаються doc_it ():
>>> new_add_ints (3, 6)
Ім'я функції: add_ints
Позиційні аргументи: (3, 6)
Ключові аргументи: {}
Результат: 9
9
>>>
Спростити декорування (замість ручного присвоювання) мо-
жна за допомогою такої конструкції:
>>> @doc_it
def add_ints(a, b):
return a + b
>>> add_ints(3, 6) # add_ints = doc_it(add_ints)
Ім'я функції: add_ints
Позиційні аргументи: (3, 6)
Ключові аргументи: {}
Результат: 9
9
>>>
Визначимо ще один декоратор:
>>> def sq_it(func):
def new_func(*args, **kwargs):
result = func(*args, **kwargs)

55
return result * result
return new_func
Першим виконається декоратор sq_it (), потім doc_it ():
>>> @doc_it
@sq_it
def add_ints(a, b):
return a + b
>>> add_ints(3, 6)
Ім'я функції: new_func
Позиційні аргументи: (3, 6)
Ключові аргументи: {}
Результат: 81
81
>>>
Від зміни порядку декораторів результат не змінюється:
>>> @sq_it
@doc_it
def add_ints(a, b):
return a + b
>>> add_ints(3, 6)
Ім'я функції: add_ints
Позиційні аргументи: (3, 6)
Ключові аргументи: {}
Результат: 9
81
>>>
Приклад додавання тегів через виклик декораторів:
def bold(fun_hello):
def inner(who):
print ("<b>")
fun_hello(who)
print ("</b>")
return inner
def italic(fun_hello):

56
def inner(who):
print ("<i>")
fun_hello(who)
print ("</i>")
return inner
@italic
@bold
def hello(who):
print ("Hello", who)
hello("World")
Виведення:
<i>
<b>
Hello World
</b>
</i>
Припустимо, що необхідно перевірити швидкість роботи де-
якої функції:
import time
def timer(f):
def tmp(*args, **kwargs):
t = time.time()
res = f(*args, **kwargs)
print ("Час виконання функції: %f" % (time.time()-t))
return res
return tmp
# декоратор, який виконує затримку переданої функції на 1
секунду
def pause(f):
def tmp(*args, **kwargs):
time.sleep(1)
return f(*args, **kwargs)
return tmp

57
@timer
@pause
def func(x, y):
return x + y
func(1, 2)
Виведення:
Час виконання функції: 1.000044

1.8.10. Оператор return

Оператор return використовується для повернення з функції,


тобто для припинення її роботи й виходу з неї. При цьому мож-
на також повернути деяке значення з функції.
def maximum(x, y):
if x > y:
return x
elif x == y:
return 'Числа рівні'
else:
return y
print(maximum(2, 3))
Виведення:
3
Як це працює. Функція maximum повертає максимальний із
двох параметрів, які в такому випадку передаються їй при ви-
клику. Вона використовує звичайний умовний оператор if..else
для визначення найбільшого числа, а потім повертає це число.
Зверніть увагу, що оператор return без вказівки значення, що
повертається, еквівалентний виразу return None. None – це спе-
ціальний тип даних у Python, що позначає "нічого". Наприклад,
якщо значення змінної встановлене в None, то це означає, що їй
не присвоєно жодного значення.
Кожна функція містить у неявній формі оператор return None
у кінці, якщо ви не вказали власний оператор return.
58
1.8.11. Рядки документації

Python має особливість, яка називається рядками документа-


ції й зазвичай скорочено позначається docstrings. Це дуже важ-
ливий інструмент, яким ви обов'язково повинні користуватися,
оскільки він допомагає краще документувати програму і полег-
шує її розуміння. Рядок документації можна отримати, напри-
клад, з функції, навіть під час виконання програми!
def printMax (x, y):
''' Виводить максимальне з двох чисел.
Обидва значення мають бути цілими числами.'''
x = int (x) # конвертуємо в цілі, якщо можливо
y = int (y)
if x > y:
print (x, 'найбільше')
else:
print (y, 'найбільше')
printMax (3, 5)
print (printMax.__doc__)
Виведення:
5 найбільше
'Виводить максимальне з двох чисел.
Обидва значення мають бути цілими числами.
Як це працює. Рядок у першому логічному рядку функції є
рядком документації для цієї функції. Рядки документації засто-
совні також до модулів і класів.
Рядки документації прийнято записувати у формі багаторядково-
го рядка, де перший рядок починається з великої літери і закінчуєть-
ся крапкою. Другий рядок залишається порожнім, а докладний опис
починається із третього. Рекомендується дотримуватися такого фо-
рмату для всіх рядків документації всіх нетривіальних функцій.
Доступ до рядка документації функції printMax можна отри-
мати за допомогою атрибута цієї функції (тобто імені, що нале-
жить їй) __doc__ (зверніть увагу на подвійне підкреслення).
Просто пам'ятайте, що Python зображує все у вигляді об'єктів,
включаючи функції.
59
1.8.12. Особливості використання
функцій у Python

Розглянемо кілька корисних особливостей при роботі з фун-


кціями в Python. Імена функцій у Python є змінними, що містять
адресу об'єкта типу функції, тому цю адресу можна присвоїти
іншій змінній і викликати функцію з іншим ім'ям.
def summa(x, y):
return x + y
f = summa
v = f(10, 3) # викликаємо функцію з іншим ім'ям
Ім'я функції – звичайна змінна, тому можемо передати її як
аргумент при виконанні функції:
def summa(x, y):
return x + y
def func(f, a, b):
return f(a, b)
v = func(summa, 10, 3) # передаємо summa як аргумент

Вправи
1. Кількість цифр числа. Написати програму з використанням
функції, що знаходить кількість C цифр цілого додатного числа
K (104 ≤ К ≤ 106), а також їхню суму S. За допомогою цієї про-
грами знайти кількість і суму цифр для кожного з п'яти випад-
кових цілих чисел. Рядкові змінні не використовувати.
На виході: випадкове ціле число і два цілих числа, розділених
пробілом.
12345 5 15
67890 5 30
13579 5 25
24680 5 20
98765 5 35
2. Інвертування числа. Написати програму з використанням
функції, яка міняє порядок проходження цифр цілого додатно-
го числа K (104 ≤ К ≤ 106) на зворотний. За допомогою цієї про-
60
грами поміняти порядок проходження цифр на зворотний
для кожного з п'яти випадкових цілих чисел. Рядкові змінні
не використовувати.
На виході: випадкове ціле число та інвертоване ціле число,
розділені пробілом.
12345 54321
67890 9876
13579 97531
24680 8642
98765 56789

3. Обчислення степеня числа. Написати програму з викорис-


танням функції, яка обчислює другий, третій і четвертий степінь
числа A та повертає ці степені відповідно в змінних B, C та D. За
допомогою цієї програми знайти другий, третій і четвертий сте-
пені п'яти випадкових чисел.
На виході: випадкове ціле число і три дійсних числа для ко-
жного випадкового значення. Виведення здійснити у вигляді
таблиці, де значення вирівняні.
3| 9.0| 27.0| 81.0
5| 25.0| 125.0| 625.0
4| 16.0| 64.0| 256.0
11|121.0| 1331.0| 14641.0
25|625.0|15625.0|390625.0

4. Середнє арифметичне і середнє геометричне чисел. Напи-


сати програму з використанням функції, яка обчислює середнє
арифметичне (X+Y)/2 і середнє геометричне X × Y двох до-
датних чисел X та Y. За допомогою цієї програми знайти серед-
нє арифметичне і середнє геометричне для пар (A, B), (A, C), (A,
D), якщо дано випадкові A, B, C, D < 100.
На виході: пара додатних чисел і два дійсних числа. Виве-
дення здійснити у вигляді таблиці, де значення вирівняні.
3.25 16.67| 9.96| 7.36054
3.25 36.49|19.87|10.89002
3.25 78.23|40.74|15.94514
5. Прямокутник. Написати програму з використанням функ-
ції, яка обчислює периметр P і площу S прямокутника зі сторо-
нами, паралельними осям координат, за координатами (x1, y1),
61
(x2, y2) його протилежних вершин (x1, y1, x2, y2 < 10). Урахува-
ти, що необхідна перевірка, чи задають координати прямокут-
ник. За допомогою цієї програми знайти периметри і площі
трьох прямокутників з даними протилежними вершинами.
На виході: дві пари координат і два дійсних числа. Виведення
здійснити у вигляді таблиці, де значення вирівняні.
| X1 | Y1 | X2 | Y2 | P | S |
| 7.45|-3.69| 2.57|-0.44|16.26|15.86|
|-7.45|-3.69|-2.57|-0.44|16.26|15.86|
|-7.45| 3.69|-2.57| 0.44|16.26|15.86|

1.8.13. Рекурсивні функції

Як ми бачили вище, функція може викликати іншу функцію.


Однак вона також може викликати й саму себе! Розглянемо це
на прикладі функції обчислення факторіала. Добре відомо, що 0!
= 1, 1! = 1. А як обчислити величину n! для великого n? Якби ми
могли вирахувати величину (n-1)!, то легко б обчислили n!,
оскільки n! = N (n-1)!. Однак як обчислити (n-1)!? Якби ми ви-
рахували(n-2)!, то обчислили б і (n-1)! = (N-1) (n-2)!. А як об-
числити (n-2)!? Якби ... Зрештою ми дійдемо до величини 0!, яка
дорівнює 1. Таким чином, для обчислення факторіала ми може-
мо використовувати значення факторіала для меншого числа:
def factorial(n):
if n == 0:
return 1
else:
return n * factorial(n – 1)
print(factorial(5))
Подібний прийом (виклик функцією самої себе) називається
рекурсією, а така функція називається рекурсивною.
Рекурсивні функції є потужним механізмом у програмуванні.
На жаль, вони не завжди ефективні. Часто використання рекурсії
призводить до помилок. Найпоширеніша з таких помилок – не-
62
скінченна рекурсія, коли ланцюжок викликів функцій ніколи не
завершується і триває, поки не скінчиться вільна пам'ять у ком-
п'ютері. Дві найпоширеніші причини для нескінченної рекурсії:
1. Неправильне оформлення виходу з рекурсії. Наприклад,
якщо ми в програмі обчислення факторіала забудемо поставити
перевірку if n == 0, то factorial (0) викличе factorial (-1), а той
викличе factorial (-2) і т. д.
2. Рекурсивний виклик з неправильними параметрами. На-
приклад, якщо функція factorial (n) буде викликати factorial (n),
то також вийде нескінченний ланцюжок.
Тому при розробці рекурсивної функції необхідно, перш за
все, оформляти умови завершення рекурсії й думати, чому реку-
рсія коли-небудь завершить роботу.
Вправи
1. Подвійний факторіал числа. Написати програму з викорис-
танням функції, яка обчислює значення подвійного факторіала
N !! = N × (N-2) × (N-4) ×. . .
(N> 0 – параметр цілого типу; останній співмножник у добутку
дорівнює 2, якщо N – парне число, і 1, якщо N – непарне). За
допомогою цієї програми обчислити подвійні факторіали п'яти
випадкових чисел.
На виході: п'ять випадкових цілих чисел та їхній подвійний
факторіал. Виведення даних здійснити у вигляді таблиці.
2. Кількість поєднань. Написати програму з використанням
функції, яка знаходить C (N, K) – кількість поєднань із N елеме-
нтів по K – за допомогою рекурентного співвідношення:
C (N, 0) = C (N, N) = 1,
C (N, K) = C (N -1, K) + C (N -1, K -1) при 0 <K <N.
Параметри функції – цілі числа; N> 0, 0 ≤ K ≤ N. Дано число
N і п'ять різних значень K. Вивести числа C (N, K) разом з кіль-
кістю рекурсивних викликів функції.
На виході: шість випадкових цілих чисел та їхнє поєднання.
Виведення даних здійснити у вигляді таблиці.
3. Сума цифр числа. Написати програму з використанням ре-
курсивної функції, яка знаходить суму цифр цілого числа K, не
використовуючи оператор циклу. За допомогою цієї програми
знайти суми цифр для п'яти випадкових цілих чисел.
63
На виході: п'ять випадкових цілих чисел і сума їхніх цифр.
Виведення даних здійснити у вигляді таблиці.
4. Наближене значення кореня. Написати програму з викори-
станням рекурсивної функції, яка знаходить наближене значен-
ня кореня K-го степеня з числа X за формулою
Y0 = 1, YN + 1 = YN – (YN – X / (YN) K-1) / K,
де YN – значення функції за фіксованих X та K. Параметри фун-
кції: X (>0) – дійсне число, K (> 1) та N (> 0) – цілі. За допомо-
гою програми знайти для даного числа X наближені значення
його кореня K-го степеня при п'яти даних значеннях N.
На виході: п'ять випадкових дійсних чисел, значення коренів.
Виведення даних здійснити у вигляді таблиці.
5. Послідовність чисел Фібоначчі 1. Написати програму з ви-
користанням рекурсивної функції, яка обчислює N-й елемент
послідовності чисел Фібоначчі:
F1 = F2 = 1, FK = FK-2 + FK-1, K = 3, 4,. . . .
За допомогою цієї програми знайти п'ять чисел Фібоначчі з
випадковими номерами та вивести їх разом з кількістю рекурси-
вних викликів функції.
На виході: п'ять випадкових цілих чисел, відповідні числа
Фібоначчі. Виведення даних здійснити у вигляді таблиці.

1.9. Використання модулів


Як можна використовувати код повторно, поміщаючи його у
функції, ми вже бачили. А що, якщо нам знадобиться повторно
використати функції в інших програмах? Як ви вже, напевно,
здогадалися, відповідь – модулі.
Існують різні способи складання модулів, але найпростіший –
створити файл із розширенням .py, що містить функції та змінні.
Інший спосіб – написати модуль тією мовою програмування,
якою написаний сам інтерпретатор Python. Наприклад, можна
писати модулі мовою програмування C, які після компіляції
використовуватимуться стандартним інтерпретатором Python.
Модуль можна імпортувати в іншу програму, щоб викорис-
товувати функції з нього. Так само ми використовуємо стандар-
тну бібліотеку Python.
64
Приклад:
import sys
print('Аргументи командного рядка:')
for i in sys.argv:
print(i)
print('\n\n Змінна PYTHONPATH містить', sys.path, '\n')
Виведення:
$ python3 using_sys.py we are arguments
Аргументи командного рядка:
using_sys.py
we
are
arguments
Змінна PYTHONPATH містить
['', 'C:\\Windows\\system32\\python30.zip',
'C:\\Python30\\DLLs', 'C:\\Python30\\lib',
'C:\\Python30\\lib\\plat-win', 'C:\\Python30',
'C:\\Python30\\lib\\site-packages']
Як це працює. На початку ми імпортуємо модуль sys командою
import. Таким чином ми повідомляємо Python, що хочемо викорис-
товувати цей модуль. Модуль sys містить функції, що належать до
інтерпретатора Python і його середовища, тобто до системи (system).
Коли Python виконує команду import sys, він шукає модуль
sys. У даному випадку – це один із вбудованих модулів, і Python
знає, де його шукати. Якби це був не скомпільований модуль,
тобто модуль, написаний мовою Python, то інтерпретатор Python
шукав би його в каталогах, перерахованих у змінній sys.path.
Якщо модуль знайдено, то виконуються команди в його тілі й
він стає доступним. Зверніть увагу, що ініціалізація18 відбува-
ється тільки при першому імпортуванні модуля.
Доступ до змінної argv в модулі sys надається за допомогою
крапки, тобто sys.argv, що явно показує: це ім'я є частиною мо-
дуля sys. Ще однією перевагою такого позначення є те, що ім'я
не конфліктує з ім'ям змінної argv, яка може використовуватися
у вашій програмі.

18
Ініціалізація – дії, що виконуються при початковому завантаженні.
65
Змінна sys.argv є списком рядків. Вона містить список аргу-
ментів командного рядка, тобто аргументів, переданих програмі
з командного рядка.
У нашому прикладі, коли ми запускаємо "python using_sys.py
we are arguments", ми запускаємо модуль using_sys.py командою
python, а все, що потрібно далі – аргументи, які передаються
програмі. Python зберігає аргументи командного рядка в змінній
sys.argv для подальшого використання.
Пам'ятайте, що ім'я сценарію19, який запускається, завжди є
першим аргументом у списку sys.argv. Отже, у наведеному при-
кладі 'using_sys.py' буде елементом sys.argv [0], 'we' – sys.argv
[1], 'are' – sys.argv [2], а 'Arguments' – sys.argv [3]. Пам'ятайте, що
в Python нумерація починається з 0, а не з 1.
sys.path містить список імен каталогів, звідки імпортуються
модулі. Зауважте, що перший рядок у sys.path порожній, він пока-
зує, що поточна директорія також є частиною sys.path, яка збіга-
ється зі значенням змінного середовища PYTHONPATH. Це
означає, що модулі, розташовані в поточному каталозі, можна
імпортувати безпосередньо. В іншому випадку доведеться поміс-
тити свій модуль в один з каталогів, перерахованих у sys.path.
Пам'ятайте, що поточний каталог – це каталог, у якому була
запущена програма. Виконайте import os; print (os.getcwd ()),
щоб дізнатися поточний каталог програми.

1.9.1. Файли байткоду .pyc

Імпортування модуля – відносно складний захід, тому Python ро-


бить деякі трюки для прискорення процесу. Один зі способів – ство-
рити байт-компільовані файли (або байткод) з розширенням .pyc, що
є певною проміжною формою, у яку Python переводить програму.
Такий файл .pyc корисний при імпортуванні модуля наступним ра-
зом у іншу програму – це відбудеться набагато швидше, оскільки
значна частина обробки, необхідної при імпортуванні, буде вже
завершена. До того ж байткод не залежить від платформи.

19
Програму мовою програмування, що є інтерпретатором, також на-
зивають сценарієм, або скриптом.
66
1.9.2. Оператор from ... import ...

Щоб імпортувати змінну argv прямо в програму й не писати


щоразу sys при зверненні до неї, можна скористатися виразом
"From sys import argv".
Для імпортування всіх імен, що використовуються в модулі
sys, можна виконати команду
"From sys import *".
Це працює для будь-яких модулів.
У загальному випадку слід уникати цього оператора, а за-
мість нього використовувати оператор import, щоб запобігти
конфлікту імен і не ускладнювати читання програми.

1.9.3. Ім'я модуля __name__

У кожного модуля є ім'я, і команди в ньому можуть дізнатися це


ім'я. Це корисно, коли потрібно знати, чи запущений модуль як
самостійна програма, чи імпортований. Як уже згадувалося вище,
коли модуль імпортується вперше, код, що міститься в ньому, ви-
конується. Ми можемо скористатися цим, щоб змусити модуль
поводитися по-різному залежно від того, використовується він сам
по собі або імпортується в іншу програму. Цього можна досягти із
застосуванням атрибуту модуля під назвою __name__.
Приклад:
if __name__ == '__main__':
print(' Ця програма запущена сама по собі.')
else:
print(' Мене імпортували в інший модуль.')
Виведення:
Ця програма запущена сама по собі.
$ python3
>>> import using_name
Мене імпортували в інший модуль.
>>>
67
Як це працює. У кожному модулі Python визначене його ім'я
– __name__. Якщо воно дорівнює '__main__', то це означає, що
модуль запущений самостійно користувачем і можна виконува-
ти відповідні дії.

1.9.4. Створення власних модулів

Створити власний модуль дуже легко, ви весь час робили це!


Адже кожна програма мовою Python також є модулем. Необхід-
но лише переконатися, що в ній установлене розширення .py.
Розглянемо приклад:
def sayhi():
print('Привіт! Це говорить мій модуль.')
__version__ = '0.1'
# Кінець модуля mymodule.py
Виведення:
Привіт! Це говорить мій модуль.
Версія 0.1
Вище наведено простий модуль. Як видно, у ньому немає ні-
чого особливого порівняно зі звичайною програмою мовою
Python. Розглянемо, як використовувати цей модуль в інших
програмах.
Пам'ятайте, що модуль має бути або в тому самому каталозі,
що і програма, у яку ми його імпортуємо, або в одному з катало-
гів, зазначених у sys.path.
Ще один модуль:
import mymodule
mymodule.sayhi()
print ('Версія', mymodule.__version__)
Виведення:
Привіт! Це говорить мій модуль.
Версія 0.1

68
Як це працює. Зверніть увагу, що ми використовуємо те саме
позначення крапкою для доступу до елементів модуля. Python
скрізь використовує те саме позначення крапкою, надаючи йо-
му, таким чином, характерний вигляд і не змушуючи нас кожно-
го разу вивчати нові способи робити що-небудь.
Розглянемо версію, яка використовує синтаксис from..import:
from mymodule import sayhi, __version__
sayhi()
print('Версія', __version__)
Виведення:
mymodule_demo2.py такий самий, як і mymodule_demo.py.
Зверніть увагу: якщо в модулі, який імпортує даний модуль,
уже було оголошене ім'я __version__, то виникне конфлікт. Це
цілком можливо, оскільки оголошувати версію будь-якого мо-
дуля за допомогою зазначеного імені – загальноприйнята прак-
тика. Тому завжди рекомендується віддавати перевагу операто-
ру import, хоча це робить програму трохи довшою.
Також можна використовувати:
from mymodule import *
Це імпортує всі публічні імена, такі як sayhi, але не імпортує
__version__, тому що воно починається з подвійного підкреслення.

1.9.5. Функція dir

Ви можете використовувати вбудовану функцію dir, щоб отри-


мати список ідентифікаторів, які об'єкт визначає. До ідентифікато-
рів модуля входять функції, класи та змінні, визначені в ньому.
Коли ви передаєте функції dir () ім'я модуля, вона повертає
список імен, визначених у цьому модулі. Якщо ніякого аргумен-
ту не передавати, то вона поверне список імен, визначених у
поточному модулі.
Приклад:
>>> import sys # отримаємо список атрибутів модуля 'sys'
>>> dir(sys)
69
['__displayhook__', '__doc__', '__excepthook__', '__name__',
__package__', '__stderr__', '__stdin__', '__stdout__', '_clear_type_cache',
'_compact_freelists','_current_frames', '_getframe', 'api_version', 'argv',
'builtin_module_names', 'byteorder', 'call_tracing', 'callstats', 'copyright',
'displayhook', 'dllhandle', 'dont_write_bytecode', 'exc_info', 'excepthook',
'exec_prefix', 'executable','exit', 'flags', 'float_info', 'getcheckinterval',
'getdefaultencoding', 'getfilesystemencoding', 'getprofile', 'getrecursionlimit',
'getrefcount', 'getsizeof','gettrace', 'getwindowsversion', 'hexversion',
'intern', 'maxsize', 'maxunicode', 'meta_path', 'modules', 'path',
'path_hooks', 'path_importer_cache', 'platform', 'prefix', 'ps1', 'ps2',
'setcheckinterval', 'setprofile', 'setrecursionlimit', 'settrace', 'stderr', 'stdin',
'stdout', 'subversion', 'version', 'version_info', 'warnoptions', 'winver']
>>> dir() # отримаємо список атрибутів поточного модуля
['__builtins__', '__doc__', '__name__', '__package__', 'sys']
>>> a = 5 # створимо нову змінну 'a'
>>> dir()
['__builtins__', '__doc__', '__name__', '__package__', 'a', 'sys']
>>> del a # видалимо ім'я 'a'
>>> dir()
['__builtins__', '__doc__', '__name__', '__package__', 'sys']
>>>
Як це працює. Спочатку ми бачимо результат застосування
dir до імпортованого модуля sys. Бачимо величезний список
атрибутів, що містяться в ньому. Потім викликаємо функцію dir,
не передаючи їй параметрів. За умовчанням вона повертає спи-
сок атрибутів поточного модуля.
Щоб спостерігати за дією dir, ми визначаємо нову змінну а та
присвоюємо їй значення, а потім знову викликаємо dir. Бачимо,
що в отриманому списку з'явилося додаткове значення. Видали-
мо змінну/атрибут з поточного модуля за допомогою оператора
del, і зміни знову відобразяться на виведенні функції dir.
Зауваження щодо del: цей оператор використовується для вида-
лення змінної/імені; після його виконання, у даному випадку – del a,
до змінної а більше неможливо звернутися – її ніби ніколи не було.
Зверніть увагу, що функція dir () працює для будь-якого об'-
єкта. Наприклад, виконайте dir ('print'), щоб побачити атрибути
функції print, або dir (str), щоб побачити атрибути класу str.
70
1.9.6. Пакети

Досі ви спостерігали деяку ієрархію в організації програм.


Змінні зазвичай містяться у функціях, функції та глобальні
змінні – у модулях. А якщо виникне необхідність організувати
модулі? Ось тут-то і знадобляться пакети.
Пакети – це просто каталоги з модулями та спеціальним фай-
лом __init__.py, який показує Python, що каталог особливий,
тому що містить модулі Python.
Уявімо, що ми хочемо створити пакет під назвою "world" із
субпакетами "asia", "africa" тощо, які, у свою чергу, будуть міс-
тити модулі "india", "madagascar" та ін.
Для цього слід було б створити таку структуру каталогів:
| – <деякий каталог із sys.path>/
| |–– world/
| |–– __init__.py
| |–– asia/
| | |–– __init__.py
| | |–– india/
| | |–– __init__.py
| | |–– foo.py
| |–– africa/
| |–– __init__.py
| |–– madagascar/
| |–– __init__.py
| |–– bar.py

Вправи
1. Площа кільця. Написати програму з використанням функ-
ції, яка знаходить площу кільця, що визначається двома колами
із загальним центром і радіусами R1 та R2 (R1 та R2 – дійсні,
R1 > R2). За її допомогою знайти площі трьох кілець, для яких
зовнішні та внутрішні радіуси випадкові.
Значення π вважати таким, що дорівнює 3.14.
Розробити власний модуль (бібліотеку) з функцією, за допо-
могою якої розраховується площа кола.
На виході: два випадкових дійсних числа і площа кільця. Ви-
ведення даних здійснити у вигляді таблиці.
71
2. Сума випадкових чисел. Написати програму з використан-
ням функції цілого типу, яка знаходить суму всіх цілих чисел
від A до B включно. Якщо A > B, то функція повертає 0. За до-
помогою цієї функції знайти суми чисел від A до B і від B до C,
якщо дано 5 наборів випадкових чисел.
Розробити власний модуль (бібліотеку) з функцією, за допо-
могою якої розраховується сума відповідних чисел.
На виході: три цілих випадкових числа і дві суми. Виведення
даних здійснити у вигляді таблиці.
3. Квадрат числа. Написати програму з використанням функ-
ції, яка повертає TRUE, якщо цілий параметр K (0≤K≤100) є
квадратом деякого цілого числа, і FALSE – в іншому випадку. За
її допомогою знайти кількість квадратів у наборі з 10 цілих до-
датних випадкових чисел.
На виході: таблиця з даними.
4. Розмір кута в радіанах. Написати програму з використан-
ням модуля, яка знаходить величину кута в радіанах, якщо дано
його величину D у градусах. Значення π вважати таким, що до-
рівнює 3.14. За допомогою програми перевести з градусів у ра-
діани п'ять випадкових кутів.
На виході: таблиця з даними.
5. Подвійний факторіал. Написати програму з використанням
модуля, яка обчислює подвійний факторіал:
N !! = 1 • 3 • 5 •. . . • N, якщо N – непарне;
N !! = 2 • 4 • 6 •. . . • N, якщо N – парне.
За допомогою цієї програми знайти подвійні факторіали п'я-
ти випадкових чисел.
На виході: таблиця з даними.

1.10. Типи даних

1.10.1. Цілочислові типи

У мові Python є два цілочислові типи, int та bool.


У логічних виразах число 0 і значення False відображують False,
а будь-яке інше ціле число і значення True відображують True.
72
У числових виразах значення True зображує 1, a False – 0. Це
означає, що можна записувати досить дивні вирази, наприклад,
вираз i + = True збільшить значення i на одиницю. Однак більш
правильним буде записувати подібні вирази як i + = 1.
 false, x = 0 or x = false,
Логічний вираз = 
true, x ≠ 0 or x = true;
 false, x = 0,
числовий вираз = 
true, x = 1.
Розмір цілого числа обмежується тільки обсягом пам'яті комп'ю-
тера, тому легко можна створити й обробляти ціле число, що скла-
дається із тисяч цифр, хоча швидкість роботи з такими числами іс-
тотно менша, ніж із числами, які відповідають машинному поданню.
Літерали цілих чисел за умовчанням записуються в десятко-
вій системі числення, але за бажання можна використовувати
інші системи числення:
>" 14600926 # десяткове число
14600926
>" 0І10111101100101011011110 # двійкове число
14600926
>" 0о67545336 і восьмеричне число
14600926
>" OxDECADE # шістнадцяткове число
14600926
Двійкові числа записуються із префіксом 0Ь, восьмеричні – із
префіксом Оо, шістнадцяткові – із префіксом Ох. У префіксах
допускається використовувати символи верхнього регістру.
При роботі з цілими числами можуть використовуватися зви-
чайні математичні функції та оператори. Деякі з функціональ-
них можливостей зображені вбудованими функціями, такими як
abs (), інші – операторами, що застосовуються до типу int.
Для всіх двомісних арифметичних операторів
(+, -, /, //, %, **)
є відповідні комбіновані оператори присвоювання:
(+=, -=, /=, //=, %=, **=).
Об'єкти можуть створюватися шляхом присвоювання літера-
лів змінним, наприклад х=17, або зверненням до імені відповід-
ного типу як до функції, наприклад х = int (17).
73
Створення об'єкта за допомогою використання його типу
може бути виконане кількома способами.
1. Створення типу даних без аргументів. У цьому випадку
об'єкт набуває значення за умовчанням, наприклад, вираз х = int
() створює ціле число 0. Будь-які вбудовані типи можуть викли-
катися без аргументів.
2. Тип викликається з єдиним аргументом. Такий спосіб ви-
користання описано в табл. 1.1.
Таблиця 1.1
Функції перетворення цілих чисел
Синтаксис Опис
bin(i) Повертає двійкове подання цілого числа i у вигляді
рядка, наприклад: bin(1980) == '0b11110111100'
hex(i) Повертає шістнадцяткове зображення цілого числа i у
вигляді рядка, наприклад: hex(1980) == '0x7bc'
int(x) Перетворює об'єкт х на ціле число; у разі помилки під
час перетворення збуджує виключення ValueError, а
якщо тип об'єкта х не підтримує перетворення на ціле
число, то збуджує виключення ТуреЕrrоr. Якщо х є
числом із плаваючою точкою, то воно перетворюється
на ціле число шляхом усічення дробової частини
int(s, Перетворює рядок s на ціле число. Якщо задано необо-
base) в'язковий аргумент base, то він має бути цілим числом у
діапазоні від 2 до 36 включно
oct(i) Повертає восьмеричне подання цілого числа i у вигляді
рядка, наприклад: oct(1980) == '0о3674'

У табл. 1.2 перераховані бітові оператори. Для всіх бітових


операторів
(|, ", &, <<, >>)
є відповідні комбіновані оператори присвоювання:
(| =, ~=, &=, <<=, >>=),
де вираз i op = j є логічним еквівалентом зображення i = i op j.
Таблиця 1.2
Бітові оператори, які застосовуються до цілих чисел
Синтаксис Опис
i|j Бітова операція OR (АБО) над цілими числами i та j
i^j Бітова операція XOR (виключне АБО) над цілими чис-
лами i та j

74
Закінчення табл. 1.2
Синтаксис Опис
i&j Бітова операція AND (І) над цілими числами i та j
i << j Зсовує значення i ліворуч на j бітів аналогічно операції
i * (2 * * j) без перевірки на переповнення
i >> j Зсовує значення i праворуч на j бітів аналогічно опера-
ції i//(2 ** j) без перевірки на переповнення
~i Інвертує біти числа i

1.10.2. Логічні значення

Існують два вбудовані логічні об'єкти: True і False. Усі вбу-


довані типи даних і типи даних зі стандартної бібліотеки можуть
бути перетворені на тип bool. Нижче наведено пару присвою-
вань логічних значень і пару логічних виразів:
>>> t = True
>>> f = False
>>> t and f
False
>>> and True
True
У мові Python є три логічні оператори: and, or та not.

1.10.3. Тип чисел із плаваючою точкою

Мова Python надає три типи значень із плаваючою точкою:


вбудовані типи float та complex і тип decimal.Decimal у стандар-
тній бібліотеці.
Тип float зображує числа з плаваючою точкою подвійної точ-
ності. У машинному поданні числа з плаваючою точкою збері-
гаються як двійкові.
Змішана арифметика підтримується таким чином, що результа-
том виразу за участю чисел типів int і float є число типу float, а за
участю типів float і complex результатом є число типу complex.
Усі числові оператори та функції, наведені в табл. 1.1, можуть за-
стосовуватися до чисел типу float, включаючи комбіновані оператори
присвоювання. Тип даних float може викликатися як функція – без
75
аргументів, повертаючи число 0.0; з аргументом типу float поверта-
ється копія аргументу, а з аргументом будь-якого іншого типу роби-
ться спроба виконати перетворення зазначеного об'єкта на тип float.
Нижче наведено приклад простої функції, що виконує порів-
няння чисел типу float на рівність у межах машинної точності:
def equal_float(a, b):
return abs(a – b) <= sys.float_info.epsilon
Щоб скористатися цією функцією, необхідно імпортувати
модуль sys. Об'єкт sys.float_info має безліч атрибутів. Напри-
клад, sys.float_info.epsilon зберігає мінімально можливу різницю
між двома числами з плаваючою точкою. Тип float у мові Python
забезпечує надійну точність до 17 значущих цифр.
Числа з плаваючою точкою можна перетворити на цілі числа
за допомогою функції int(), яка повертає цілу частину і відкидає
дробову, або за допомогою функції round (), яка враховує вели-
чину дробової частини, або за допомогою функцій math.floor ()
та math.ceil (), які округлюють до найближчого цілого.
Числа з плаваючою точкою також можуть бути зображені у
вигляді рядків у шістнадцятковому форматі за допомогою мето-
ду float.hex (). Зворотне перетворення може бути виконане за
допомогою методу float.fromhex (). Наприклад:
s = 14.25.hex() # str s == '0x1.c800000000000p+3'
f = float.fromhex(s) # float f == 14.25
t = f.hex() # str t == •0x1.c800000000000p+3'
Додатково до вбудованих функціональних можливостей ро-
боти з числами типу float модуль math надає безліч функцій, які
наведено в табл. 1.3.
Таблиця 1.3
Функції та константи модуля math
Синтаксис Опис
math.acos(x) Повертає арккосинус х у радіанах
math.asin(x) Повертає арксинус х у радіанах
math.atan(x) Повертає арктангенс х у радіанах
math.atan2(y, x) Повертає арктангенс у/х у радіанах
Повертає x, тобто найменше ціле число типу int, що
math.ceil(x)
більше та дорівнює х, наприклад math.ceil(5.4) == 6
math.copysign(x,y) Повертає х зі знаком числа у
math.cos(x) Повертає косинус х у радіанах
76
Закінчення табл. 1.3
Синтаксис Опис
math.degrees(r) Перетворює число r типу float із радіанів на градуси
Константа е, що приблизно дорівнює значенню
math.e
2.7182818284590451
math.exp(x) Повертає ех, тобто math.е ** х
Повертає |х|, тобто абсолютне значення х у ви-
math.fabs(x)
гляді числа типу float
math.factorial(x) Повертає х!
Повертає x, тобто найменше ціле число типу int,
math.floor(x) що менше та дорівнює х, наприклад
math.floor(5.4) == 5
Виконує ділення за модулем (повертає залишок)
math.fmod(x, y) числа х на число у; дає точніший результат, ніж
оператор %, стосовно чисел типу float
Повертає суму значень в ітераційному об'єкті i у
math.fsum(i)
вигляді числа типу float
Обчислює відстань від початку координат до
math.hypot(x, y)
точки (х, у)
Повертає True, якщо значенням х типу float є
math.isinf(x)
нескінченність (±inf (±∞))
math.isnan(x) Повертає True, якщо значення х типу float не є числом
math.ldexp(m, e) Повертає m×2е – операція, зворотна math.fгехр()
Повертає logb x; аргумент b є необов'язковим і за
math.log(x, b)
умовчанням має значення math.e
math.Iog10(x) Повертає log10 x
Повертає loge(l+x); дає точні значення, навіть
math.loglp(x)
коли значення х близьке до 0
Повертає дробову та цілу частини числа х у ви-
math.modf(x)
гляді двох значень типу float
Константа π, що приблизно дорівнює
math.pi
3.1415926535897931
math.pow(x, у) Повертає ху у вигляді числа типу float
math.radians(d) Перетворює число d типу float із градусів на радіани
math.sin(x) Повертає синус х у радіанах
math.sqrt(x) Повертає √x
Повертає суму значень в ітераційному об'єкті i у
math.sum(i)
вигляді числа типу float
math.tan(x) Повертає тангенс х у радіанах
Повертає цілу частину числа х у вигляді значення
math.trunc(x)
типу int; те саме, що i int(x)
77
Нижче наведено кілька фрагментів програмного коду, що де-
монструють, як можна використовувати функціональні можли-
вості модуля:
>>> import math
>>> math, pi * (5 ** 2)
78.539816339744831
>>> math.hypot(5, 12)
13.0
>>> math.modf(13.732)
(0.73199999999999932, 13.0)
Функція math.hypot () обчислює відстань від початку коорди-
нат до точки (х, у) і дає той самий результат, що й вираз
math.sqrt ((x ** 2) + (у ** 2)).

1.10.4. Рядки

Рядки в мові Python зображені незмінним типом даних str,


який зберігає послідовність символів Юнікоду. Функція str ()
може також використовуватися як функція перетворення. У
цьому випадку перший аргумент має бути рядком або об'єктом,
який можна перетворити на рядок, до того ж функції може бути
передано до двох необов'язкових рядкових аргументів, один з
яких визначає тип кодування, а другий – порядок обробки по-
милок кодування.
Ви вже знаєте, що літерали рядків створюються із викорис-
танням лапок і апострофів, при цьому важливо, щоб з обох кін-
ців літерала були лапки одного типу. Додатково можна викорис-
товувати рядки в потрійних лапках, тобто рядки, які починають-
ся і закінчуються трьома символами лапки (або трьома лапками,
або трьома апострофами).
Наприклад:
text = '''Рядки в потрійних лапках можуть включати' апостро-
фи 'і "лапки" без зайвих формальностей. Ми можемо навіть ек-
ранувати символ переведення рядка \, завдяки чому даний конк-
ретний рядок буде займати лише два рядки. '''

78
а = " Тут 'апострофи' можна не екранувати, а \ "лапки \" дове-
деться."
b = 'Тут \' апострофи \ 'доведеться екранувати, а "лапки" не
обов'язково.'
У мові Python символ переведення рядка інтерпретується як
завершальний символ інструкції, але не всередині круглих (),
квадратних [], фігурних {} дужок і рядків у потрійних лапках.
Символи переведення рядка можуть використовуватися в рядках
у потрійних лапках, можна включати символи переведення ряд-
ка в будь-які рядкові літерали за допомогою екранованої послі-
довності \ n. Усі екрановані послідовності, допустимі в мові
Python, перераховані в табл. 1.4.
Таблиця 1.4
Екрановані послідовності в мові Python
Послідовність Значення
\переведення_рядка Екранує (тобто ігнорує) символ переведення рядка
\\ Символ зворотного слешу (\)
\' Апостроф (')
\” Лапки (“)
\а Символ ASCII "сигнал" (bell, BEL)
\b Символ ASCII "видалення" (backspace, BS)
\f Символ ASCII "переведення формату" (formfeed, FF)
\n Символ ASCII "переведення рядка" (linefeed, LF)
\N{назва} Символ Юнікоду із заданою назвою
\ооо Символ із заданим восьмеричним кодом
Символ повернення каретки ASCII (carriage
\r
return, CR)
\t Символ табуляції ASCII (tab, TAB)

Якщо потрібно записати довгий рядковий літерал, що займає


два або більше рядків, але без потрійних лапок, то можна вико-
ристовувати один із прийомів, показаних нижче:
t = "Це не найкращий спосіб об'єднання двох довгих рядків, "+ \
"Тому що він заснований на використанні некоректного ек-
ранування"
s = ("Це гарний спосіб об'єднати два довгі рядки,
тому що він заснований на конкатенації рядкових літералів.")
79
Зверніть увагу, що в другому випадку для створення єдиного
виразу ми повинні були використовувати круглі дужки.
Для визначення коду символу Юнікоду (ціле число, зв'язане
із символом у кодуванні Юнікод) у рядку можна використовува-
ти вбудовану функцію ord ().
Так само можна перетворити будь-яке ціле число, яке є допу-
стимим кодом деякого символу Юнікоду, скориставшись функ-
цією chr ().
Якщо треба вивести тільки символи ASCII, то можна скорис-
татися вбудованою функцією ascii ().
Рядки підтримують звичайні оператори порівняння <, <=,
==,! =,>,> =. Ці оператори виконують порівняння рядків у пам'я-
ті за байтами.
Отримання зрізів рядків. Окремі елементи послідовності, а от-
же, і окремі символи в рядках можуть вилучатись за допомогою
оператора доступу до елементів ([]). Насправді цей оператор набага-
то універсальніший і може використовуватися для отримання не
тільки одного символу, а й цілих комбінацій елементів або символів,
якщо його застосовувати в контексті оператора вилучення зрізу.
Спочатку розглянемо можливість вилучення окремих симво-
лів. Нумерація позицій символів у рядках починається з 0 і про-
довжується до значень довжини рядка -1. На рис. 1.4 показано,
як нумеруються позиції символів у рядку, якщо припустити, що
було виконано присвоювання s = "Light ray".

Рис. 1.4

Спроба звернення до індексу за межами рядка (або будь-якого


індексу в порожньому рядку) викличе виключення IndexError.
Оператор отримання зрізу має три форми запису:
seq[start]
seq[start:end]
seq[start;end:step]

80
Посилання seq може зображувати будь-яку послідовність, та-
ку як список, рядок або кортеж. Значення start, end та step мають
бути цілими числами (або змінними, що зберігають цілі числа).
У першому випадку вилучається елемент послідовності з індек-
сом start. Друга форма запису вилучає підрядок, починаючи з
елемента з індексом start і закінчуючи елементом з індексом end,
не включаючи його. Третю форму запису розглянемо пізніше.
При використанні другої форми запису (з одною двокрап-
кою) ми можемо опустити будь-який з індексів. Якщо опустити
початковий індекс, то за умовчанням буде використовуватися
значення 0. Якщо опустити кінцевий індекс, то за умовчанням
буде використовуватися значення len (seq). Отже, якщо опусти-
ти обидва індекси, наприклад s [:], то це буде рівносильно ви-
слову s [0: len (s)]. У результаті буде залучена (скопійована)
послідовність цілком.
На рис. 1.5 наведено деякі приклади вилучення зрізів з рядка s,
отриманого в результаті присвоювання s = "The waxwork man".

Рис. 1.5

Один зі способів вставити підрядок у рядок полягає в поєд-


нанні операторів вилучення зрізу й операторів конкатенації.
Наприклад:
>>> s = s[: 12] + "wo" + s[12:]
>>> s
'The waxwork woman'
Оскільки текст "wo" є в оригінальному рядку, то того самого
ефекту можна досягнути шляхом надання значення виразу
s [: 12] + s [7: 9] + s [12:].
Оператор конкатенації + і додавання підрядка + = не дуже
ефективні, коли в операції беруть участь безліч рядків. Для об'-
єднання великої кількості рядків зазвичай краще використовува-
ти метод str.join ().

81
Третя форма запису (із двома двокрапками) нагадує другу,
але тут значення step визначає, на якому кроці слід вилучати
символи. Як і при використанні другої форми запису, можна
опустити будь-який з індексів. Якщо опустити початковий ін-
декс, то за умовчанням буде використовуватися значення 0. Як-
що опустити кінцевий індекс, то за умовчанням буде використо-
вуватися значення len (seq). Ми не можемо опустити значення
step і воно не може дорівнювати нулю – якщо задання кроку не
потрібне, то слід використовувати другу форму запису (з одною
двокрапкою), у якій крок вибору елементів не вказується.
На рис. 1.6 наведено два приклади вилучення розріджених
зрізів з рядка s, який отриманий у результаті присвоювання s =
"he ate camel food".

Рис. 1.6

Тут використано значення за умовчанням для початкового і


кінцевого індексів, тобто вилучення зрізу s [:: – 2] починається з
останнього символу рядка і вилучається кожен другий символ у
напрямку до початку рядка. Аналогічно вилучення зрізу s [:: 3]
починається з першого символу рядка і вилучається кожен тре-
тій символ у напрямку до кінця рядка.
Оператори й методи рядків. Оскільки рядки належать до
категорії незмінних послідовностей, то всі функціональні мож-
ливості, застосовні до незмінних послідовностей, можуть вико-
ристовуватися і для рядків.
Оскільки рядки є послідовностями, то вони є об'єктами, що
мають розмір, тому ми можемо викликати функцію len (),
передаючи їй рядки як аргументи. Повертається функцією
довжина рядка, тобто кількість символів у рядку (нуль – для
порожніх рядків).

82
Ми вже знаємо, що перевантажена версія оператора "+" для
рядків виконує операцію конкатенації. У випадках, коли необ-
хідно об'єднати безліч рядків, краще використовувати метод
str.join (). Метод приймає як аргумент послідовності й об'єднує
їх у єдиний рядок, вставляючи між ними рядок, для якого був
викликаний метод. Наприклад:
>>> treatises = ["Arithmetica", "Conies", "Elements"]
>>> str.join(treatises)
'Arithmetica Conies Elements'
Метод str.join () може використовуватися в комбінації зі вбу-
дованою функцією reversed (), яка перевертає рядок, наприклад
str.join (reversed (s)). Однак той самий результат можна отрима-
ти коротшим оператором вилучення розрідженого зрізу, напри-
клад s [:: -1].
Оператор "*" забезпечує можливість дублювання рядка:
>>> s = “=” * 5
>>> print(s)

>>> s *= 10
>>> print(s)
Як показано в прикладі, ми можемо також використовувати
комбінований оператор присвоювання з дублюванням.
Коли оператор перевірки на входження in застосовується до
рядків, він повертає True, якщо операнд ліворуч є підрядком
операнда праворуч або дорівнює йому. Коли необхідно точно
визначити позицію підрядка в рядку, можна використовувати
два методи. Перший метод str.index () повертає позицію підряд-
ка в рядку або збуджує виключення ValueError, якщо підрядок
не знайдено. Другий метод str.find () повертає позицію підрядка
в рядку або -1 – у випадку невдачі. Обидва методи приймають
шуканий підрядок як перший аргумент і можуть приймати ще
пару необов'язкових аргументів. Другий аргумент визначає по-
зицію початку пошуку, а третій – позицію закінчення пошуку.
Коли ми отримуємо рядки із зовнішніх джерел (з інших про-
грам, з файлів, через мережні з'єднання або в результаті взаємо-
дії з користувачем), вони можуть містити на початку або в кінці
небажані пробільні символи. Видалити пробільні символи, що
83
містяться на початку рядка, можна за допомогою методу
str.lstrip (), у кінці рядка – методу str.rstrip (), а з обох кінців – за
допомогою методу str.strip (). Ми можемо також передавати
методам сім'ї * strip рядки, у цьому випадку вони видалять кож-
не входження кожного символу з відповідного кінця (або з обох
кінців) рядка. Наприклад:
>>> s = "\t no parking "
>>> s.lstrip(), s.rstrip(), s.strip()
('no parking ', *\t no parking', 'no parking')
Можна також заміщати підрядки в рядках, використовуючи
метод str.replace(). Цей метод приймає два рядкові аргументи й
повертає копію рядка, у контексті якого був викликаний метод, де
кожне входження рядка в першому аргументі заміщене рядком у
другому аргументі. Якщо другий аргумент є порожнім рядком, то
видаляються всі входження рядка в першому аргументі.
Часто буває необхідно розбити рядок на список рядків. На-
приклад, у нас може бути текстовий файл з даними, у якому
одному рядку відповідає один запис, а поля всередині запису
відокремлюються одне від одного зірочками. Реалізувати таке
розбиття можна за допомогою методу str.split (), передавши йо-
му рядок, на якому виконується розбиття, у вигляді першого
аргументу, і максимальну кількість розбиттів – у вигляді друго-
го, необов'язкового аргументу. Якщо другий аргумент не вказа-
но, то виконується стільки розбиттів, скільки потрібно. Нижче
наведено приклад використання методу:
>>> record = "Leo Tolstoy*1828-8-28*1910-11-20"
>>> fields = record.split("*")
>>> fields
['Leo Tolstoy', '1828-8-28', '1910-11-20']
Форматування рядків за допомогою методу str.format ().
Метод str.format () є дуже потужним і гнучким засобом створення
рядків. Його використання в простих випадках не викликає труд-
нощів, але для складніших треба знати синтаксис форматування.
Метод str.format () повертає новий рядок, заміщаючи поля в
контекстному рядку відповідними аргументами. Наприклад:
>>> "The novel {0} was published in {1}".format("Hard Times", 1854)
"The novel 'Hard Times' was published in 1854"
84
Кожне заміщене поле ідентифікується ім'ям поля у фігурних
дужках. Якщо як ім'я поля використовується ціле число, то воно
визначає порядковий номер аргументу, переданого методу
str.formate (). Тому в даному випадку поле з ім'ям 0 було замі-
щене першим, а поле з ім'ям 1 – другим аргументом.

1.10.5. Час і дата

Модулі calendar і datetime містять функції та класи, призна-


чені для роботи з датами й часом. Однак вони засновані на абст-
рактному григоріанському календарі, тому не годяться для ро-
боти з датами в календарях, що йому передували. Дата і час – це
дуже складна тема. У різний час і в різних місцях використову-
валися різні календарі. Тривалість доби не дорівнює точно
24 год, тривалість року не дорівнює точно 365 дням, існують
літній і зимовий час, а також різні часові пояси.
Модуль time працює з позначками часу, які є простими чис-
ловими значеннями та зображують кількість секунд, що минули
від початку епохи (1970-01-01Т00: 00: 00 в UNIX). Він може
використовуватися для отримання на машині позначок поточно-
го (клік) UTC20 або локального часу, що враховує перехід на
літній час, для створення рядків, що зображують дату, час і да-
ту/час, відформатованих різними способами, а також для аналізу
рядків, що містять дату і час.
Поточні дату/час UTC можна отримати у вигляді об'єкта
datetime.datetime, викликавши функцію datetime.datetime.utcnow
(), а у вигляді кількості секунд, що минули з початку епохи, –
викликавши функцію time.time (). Для отримання локальних
дати/часу можна використовувати datetime.datetime.now() або
time.mktime(time.localtime ()).
Вправи
1. Кількість слів. Ввести з клавіатури символьний рядок
To(1)be,(2)or(3)not(4)to(3)be(2)that(1)is(2)the(3)question(4)…
і визначити, скільки в ньому слів (число між словами вказує
кількість пробілів). Видалити зайві пробіли.
20
Coordinated Universal Time – Універсальний скоординований час.
85
2. Видалити кожен третій символ. Дано рядок
In the hole in the ground there lived a hobbit.
Видаліть із нього всі символи, чиї індекси діляться на 3.
3. Арифметичний вираз. Програма обчислює арифметичний
вираз, записаний у нормальній інфіксній21 формі, виводячи ре-
зультат. Арифметичний вираз складається із трьох цілих чисел і
знаків арифметичних операцій: "+", "-", "/", "*".
4. Шифрування рядка 1. Дано рядок. Зашифрувати його, вико-
навши циклічну заміну кожної літери на наступну в алфавіті й збе-
рігши при цьому регістр літер ("А" перейде в "Б", "а" – у "б", "Б" –
у "В", "я" – в "а" і т. д.). Розділові знаки і пробіли не змінювати.
5. Шифрування рядка 2. Дано рядок і число K (0 <K <10).
Зашифрувати рядок, виконавши циклічну заміну кожної літери
на літеру того самого регістру, розташовану в алфавіті на K-й
позиції після літери, що шифруємо (наприклад, для K = 2 "А"
перейде у "В", "а" – у "в", "б" – у "Г", "я" – у "б" і т. д.).

1.11. Списки, кортежі та словники


Структури даних – це конструкції даних, які можуть зберіга-
ти деякі дані разом. Іншими словами, вони використовуються
для зберігання зв'язаних даних.
У Python існують чотири вбудовані структури даних: список,
кортеж, словник і множина.

1.11.1. Список

Список – це структура даних, яка містить упорядкований на-


бір елементів, тобто зберігає послідовність елементів. Це легко
уявити, якщо згадати список покупок, у якому перераховується,
що потрібно купити, з тим лише винятком, що в списку покупок
кожен елемент зазвичай розміщується на окремому рядку, тоді
як у Python вони розділяються комами.

21
Інфіксна нотація – це форма запису математичних і логічних фо-
рмул, у якій оператори записані в інфіксному стилі між операндами, на
які вони впливають (наприклад 2 + 2).
86
Список елементів має бути у квадратних дужках, щоб Python
зрозумів, що це список. Як тільки список створений, можна до-
давати, видаляти або шукати елементи в ньому.
Оскільки елементи можна додавати й видаляти, то ми кажемо,
що список – це змінний тип даних, тобто його можна модифікувати.
NameList = [Item1, Item2,…,ItemN]
Короткий вступ до об'єктів і класів. Список – це один із при-
кладів використання об'єктів і класів. Коли ми призначаємо деякій
змінній i значення, припустимо, ціле число 5, це можна зобразити
як створення об'єкта (тобто екземпляра) i класу (тобто типу) int.
Клас може мати методи, тобто функції, визначені для викорис-
тання лише в ньому. Такий функціонал буде доступним, тільки
коли є об'єкт даного класу. Наприклад, Python надає метод append
для класу list, який дозволяє додавати елемент у кінець списку.
Наприклад, mylist.append ('and item') додає рядок до списку mylist.
Зверніть увагу на крапку для доступу до методів об'єктів.
Клас може мати поля (властивості), які є не чим іншим, як
змінними, застосовними тільки в ньому. Ці змінні/імена можна
використовувати лише тоді, коли є об'єкт зазначеного класу.
Доступ до властивостей також здійснюється за допомогою кра-
пки, наприклад: mylist.field.
# Це мій список покупок
shoplist = ['Яблука', 'манго', 'морква', 'банани ']
print('Я повинен зробити ', len(shoplist), 'покупок.')
for item in shoplist:
print('Покупки:', item, end=' ')
print('\n Також потрібно купити рису.')
shoplist.append('рис')
print('Тепер мій список покупок такий :', shoplist)
print('Відсортую-ка я свій список')
shoplist.sort()# у дужках може бути сортувальна функція
print('Відсортований список покупок має такий вигляд:',
shoplist)
print('Перше, що мені потрібно купити, це', shoplist[0])

87
olditem = shoplist[0]
del shoplist[0]
print('Я купив', olditem)
print('Тепер мій список покупок:', shoplist)
Виведення:
Я повинен зробити 4 покупки.
Покупки: яблука манго морква банани
Також потрібно купити рису.
Тепер мій список покупок такий: ['яблука', 'манго', 'морква',
'банани', 'рис']
Відсортую-ка я свій список
Відсортований список покупок має такий вигляд: ['банани',
'манго', 'морква', 'рис', 'яблука']
Перше, що мені потрібно купити, це банани
Я купив банани
Тепер мій список покупок: ['манго', 'морква', 'рис', 'яблука']
Як це працює. Змінна shoplist – це список покупок. У список
можна додавати будь-які об'єкти, включаючи числа або навіть
інші списки.
Ми також використовували цикл for..in для ітерації за елеме-
нтами списку. Ви вже, напевно, зрозуміли, що список є також
послідовністю.
Зверніть увагу на використання ключового аргументу end у
функції print, який показує, що ми хочемо закінчити виведення
пробілом замість звичайного переведення рядка.
Далі додаємо елемент до списку за допомогою append. Потім
перевіряємо, чи дійсно елемент був доданий до списку, виводя-
чи зміст списку на екран за допомогою простої передачі цього
списку функцією print.
Потім сортуємо список, використовуючи метод sort об'єкта
списку. Майте на увазі, що цей метод діє на сам список, а не
повертає його змінену версію.
Далі вказуємо, що хочемо видалити перший елемент списку,
для чого пишемо "del shoplist [0]" (пам'ятаєте, що Python почи-
нає відлік з 0).
88
Псевдоніми та копіювання списків. Розглянемо важливу
особливість списків:
>>> h
['bonjour', 7, 'hola', -1.0, 'привіт']
>>> p=h # містять покажчик на той самий список
>>> p
['bonjour', 7, 'hola', -1.0, 'привіт']
>>> p[0]=1 # модифікуємо одну зі змінних
>>> h # змінилася інша змінна!
[1, 7, 'hola', -1.0, 'привіт']
>>> p
[1, 7, 'hola', -1.0, 'привіт']
>>>
У Python дві змінні називаються псевдонімами22, якщо вони
містять однакові адреси пам'яті.
На рис. 1.7 видно, що змінні p та h указують на той самий список.

Рис. 1.7

Створення псевдонімів – особливість списків, тому що вони


можуть змінюватися. Будьте вкрай уважні!
Розглянемо, як перевірити, чи посилаються змінні на той са-
мий список:
>>> x = y = [1, 2] # створення псевдонімів
>>> x is y # перевірка, чи посилаються змінні на той самий об'єкт23

22
Псевдоніми – альтернативні імена.
23
За допомогою is порівнюються посилання-адреси, а не самі об'єкти.
89
True
>>> x = [1, 2]
>>> y = [1, 2]
>>> x is y
False
>>>
До списків застосовуються два види копіювання. Перший – по-
верхневе копіювання, за якого створюється новий об'єкт, але він бу-
де заповнений посиланнями на елементи, що містилися в оригіналі:
>>> a = [4, 3, [2, 1]]
>>> b = a[:]
>>> b is a
False
>>> b[2][0]=-100
>>> a
[4, 3, [-100, 1]] # список a також змінився
>>>
Другий вид – глибоке копіювання. За глибокого копіювання
створюються новий об'єкт і рекурсивно – копії всіх об'єктів, що
містяться в оригіналі:
>>> import copy
>>> a = [ 4, 3, [2, 1] ]
>>> b = copy.deepcopy(a)
>>> b[2][0]=-100
>>> a
[4, 3, [2, 1]] # список a не змінився
>>>

1.11.2. Кортеж

Кортежі служать для зберігання кількох об'єктів разом. Їх


можна розглядати як аналог списків, але без такої великої функ-
ціональності, яку надає клас списку. Одна з найважливіших осо-
бливостей кортежів полягає в тому, що вони незмінні, так само
як рядки, тобто модифікувати кортежі неможливо.

90
Кортежі утворюються перелічуванням елементів, розділених
комами; за бажанням їх можна поміщати в круглі дужки.
Кортежі зазвичай використовуються в тих випадках, коли
оператор або призначена для користувача функція мають напев-
но знати, що набір значень, тобто кортеж значень, не зміниться.
zoo = ('пітон', 'слон', 'пінгвін') # пам'ятайте, що дужки не обо-
в'язкові
print('Кількість тварин у зоопарку -', len(zoo))new_zoo = 'мав-
па', 'верблюд', zoo
print('Кількість тварин у зоопарку -', len(new_zoo))
print('Усі тварини в новому зоопарку:', new_zoo)
print('Тварини, що привезені зі старого зоопарку:',
new_zoo[2])
print('Остання тварина, що привезена зі старого зоопарку -',
new_zoo[2][2])
print('Кількість тварин у новому зоопарку -', len(new_zoo)-
1+len(new_zoo[2]))
Виведення:
Кількість тварин у зоопарку – 3
Кількість кліток у зоопарку – 3
Усі тварини в новому зоопарку: (мавпа, 'верблюд', ('пітон',
'слон','пінгвін'))
Тварини, що привезені зі старого зоопарку: ('пітон', 'слон',
'пінгвін')
Остання тварина, що привезена зі старого зоопарку – пінгвін
Кількість тварин у новому зоопарку – 5
Як це працює. Змінна zoo позначає кортеж елементів. Функ-
ція len дозволяє отримати довжину кортежу. Це також указує на
те, що кортеж є послідовністю.
Тепер ми переміщаємо тварин у новий зоопарк, оскільки ста-
рий закривається. Тому кортеж new_zoo містить тварин, які вже
є там, разом із привезеними зі старого зоопарку. Зверніть увагу,
що кортеж усередині кортежу не втрачає індивідуальності.
Доступ до елементів кортежу здійснюється за значенням по-
зиції елемента, поміщеного у квадратні дужки, так само, як ми
робили для списків. Доступ до третього елемента в new_zoo ми

91
отримуємо, указуючи new_zoo[2], а доступ до третього елемента
всередині третього елемента в кортежі new_zoo – указуючи
new_zoo [2] [2]. Це досить просто, якщо розуміти принцип.
Дужки. Хоча дужки не є обов'язковими, усе ж таки краще
завжди вказувати їх, щоб було очевидно, що це кортеж, особли-
во у двозначних випадках. Наприклад, "print (1,2,3)" і "print
((1,2,3))" роблять різні речі: перший вираз виводить три числа,
тоді як другий – кортеж, що містить ці три числа.
Кортеж, що містить 0 або 1 елемент. Порожній кортеж
створюється за допомогою порожньої пари дужок – "myempty =
()". Однак із кортежем з одного елемента не все просто. Його
потрібно вказувати за допомогою коми після першого (і єдино-
го) елемента, щоб Python міг відрізнити кортеж від дужок, що
оточують об'єкт у виразі. Таким чином, щоб отримати кортеж,
який містить елемент 2, треба вказати "singleton = (2,)".

1.11.3. Словник

Словник – це перелік ключів (імен), зв'язаних зі значеннями


(інформацією). Ключ має бути унікальним. У словниках як ключі
можуть використовуватися тільки незмінні об'єкти (наприклад
рядки), а за значення можна брати як незмінні, так і змінні об'єкти.
Пари ключ – значення вказуються в словнику таким чином:
d = {key1: value1, key2: value2}.
Ключ і значення розділяються двокрапкою, пари одна від од-
ної відокремлюються комами, а потім все це поміщається у фі-
гурні дужки.
Пари ключ – значення ніяк не впорядковані в словнику. Як-
що вам необхідний деякий порядок, то доведеться окремо впо-
рядкувати словник перед зверненням до нього:
# 'ab' – скорочення від 'a'ddress'b'ook
ab = { 'Swaroop' : 'swaroop@swaroopch.com',
'Larry' : 'larry@wall.org',
'Matsumoto' : 'matz@ruby-lang.org',
'Spammer' : 'spammer@hotmail.com'
}
print("Адреса Swaroop'а:", ab['Swaroop'])
92
# Видалення пари ключ – значення del ab['Spammer']
print('\n В адресній книзі {0} контактів\n'.format(len(ab)))
for name, address in ab.items():
print('Контакт {0} з адресою {1}'.format(name, address))
# Додавання пари ключ – значення
ab['Guido'] = 'guido@python.org'
if 'Guido' in ab:
print("\nАдреса Guido:", ab['Guido'])
Виведення:
Адреса Swaroop'а: swaroop@swaroopch.com
В адресній книзі 3 контакти
Контакт Swaroop з адресою swaroop@swaroopch.com
Контакт Matsumoto з адресою matz@ruby-lang.org
Контакт Larry з адресою larry@wall.org
Адреса Guido: guido@python.org
Як це працює. Ми створюємо словник ab за допомогою по-
значень, описаних раніше. Потім звертаємося до пари ключ –
значення, указуючи ключ в операторі індексування, яким ми
користувалися для списків і кортежів. Отже, синтаксис простий.
Видаляти пари ключ – значення можна за допомогою оператора
del. Ми просто вказуємо ім'я словника і оператор індексування для
видалення ключа, після чого передаємо це оператору del. Для такої
операції не обов'язково знати, яке значення відповідає даному ключу.
Далі звертаємося до всіх пар ключ – значення нашого словника,
використовуючи метод items, який повертає список кортежів, кожен
із яких містить пару елементів: ключ і значення. Отримуємо шукану
пару і присвоюємо її значення змінним name та address, відповідно,
у циклі for..in, а потім виводимо ці значення на екран у блоці for.
Нові пари ключ – значення додаються простим зверненням
до потрібного ключа за допомогою оператора індексування і
присвоюванням йому деякого значення, як ми робили для Guido
у прикладі вище.
Перевірити, чи існує пара ключ – значення, можна за допо-
могою оператора in.

93
1.11.4. Послідовності

Списки, кортежі й рядки є прикладами послідовностей. Од-


нак що таке послідовності та що в них особливого?
Основні можливості – це перевірка приналежності (тобто ви-
раження "in" і "not in") і оператор індексування, що дозволяє
отримати безпосередньо певний елемент послідовності.
Усі три типи послідовностей, згадувані вище (списки, корте-
жі й рядки), також надають операцію вирізання, що дозволяє
отримати зріз послідовності, тобто її фрагмент.
shoplist = ['яблука', 'манго', 'морква', 'банани']
name = 'swaroop'
# Операція індексування
print('Елемент 0 -', shoplist[0])
print('Елемент 1 -', shoplist[1])
print('Елемент 2 -', shoplist[2])
print('Елемент 3 -', shoplist[3])
print('Елемент -1 -', shoplist[-1])
print('Елемент -2 -', shoplist[-2])
print('Символ 0 -', name[0])
# Вирізка зі списку
print('Елементи з 1 до 3:', shoplist[1:3])
print('Елементи з 2 до кінця:', shoplist[2:])
print('Елементи з 1 до -1:', shoplist[1:-1])
print('Елементи від початку до кінця:', shoplist[:])
# Вирізка з рядка
print('Символи з 1 до 3:', name[1:3])
print('Символи з 2 до кінця:', name[2:])
print('Символи з 1 до -1:', name[1:-1])
print('Символи від початку до кінця:', name[:])
Виведення:
Елемент 0 – яблука
Елемент 1 – манго
Елемент 2 – морква
Елемент 3 – банани
Елемент -1 – банани

94
Елемент -2 – морква
Символ 0 – s
Елементи з 1 до 3: ['манго', 'морква']
Елементи з 2 до кінця: ['морква', 'банани']
Елементи з 1 до -1: ['манго', 'морква']
Елементи від початку до кінця: ['яблука', 'манго', 'морква',
'банани']
Символи з 1 до 3: wa
Символи з 2 до кінця: aroop
Символи з 1 до -1: waroo
Символи від початку до кінця: swaroop
Як це працює. Перш за все, ми бачимо, як використовувати
індекси для отримання окремих елементів послідовності (це ще
називають приписуванням індексу). Коли ми вказуємо число у
квадратних дужках після послідовності, як показано вище,
Python відображає елемент, що відповідає наведеній позиції в
послідовності. Пам'ятайте, що Python починає відлік з 0. Тому
shoplist [0] відображає перший, а shoplist [3] – четвертий еле-
мент послідовності shoplist.
Індекс також може бути від'ємним числом. У цьому випадку
позиція відраховується від кінця послідовності. Тому shoplist [-
1] указує на останній елемент послідовності shoplist, а shoplist [-
2] – на передостанній.
Операція вирізання виконується за допомогою указування
імені послідовності, за яким може йти пара чисел, розділених
двокрапкою і поміщених у квадратні дужки. Це схоже на опера-
цію індексування, яку ми застосовували досі. Пам'ятайте, що
числа в дужках необов'язкові, тоді як двокрапка – обов'язкова.
Перше число (перед двокрапкою) в операції вирізання вказує
на позицію, з якої зріз має починатися, а друге (після двокрапки)
– де він має закінчитися. Якщо перше число не вказане, то
Python почне вирізання з початку послідовності. Якщо пропу-
щене друге число, то Python закінчить вирізання в кінці послі-
довності. Зверніть увагу, що отримана вирізка буде починатися
зі вказаної початкової позиції, а закінчуватися перед зазначеною
кінцевою позицією, тобто початкова позиція буде включена у
вирізку, а кінцева – ні.
95
Таким чином, shoplist [1: 3] повертає вирізку з послідовності,
що починається з позиції 1, включає позицію 2, але зупиняється
на позиції 3 і тому повертає вирізку з двох елементів. Аналогіч-
но shoplist [:] повертає копію всієї послідовності.
Вирізання може здійснюватися і з від'ємними значеннями.
Від'ємні числа позначають позицію з кінця послідовності. На-
приклад, shoplist [: – 1] поверне вирізку з послідовності, яка ви-
ключає останній елемент, але містить усі інші.
Крім того, можна також указати третій аргумент для вирізання,
що буде позначати крок вирізання, який за умовчанням дорівнює 1:
>>> shoplist = ['яблука', 'манго', 'морква', 'банани']
>>> shoplist[::1]
['яблука', 'манго', 'морква', 'банани']
>>> shoplist[::2]
['яблука', 'морква']
>>> shoplist[::3]
['яблука', 'банани']
>>> shoplist[::-1]
['банани', 'морква', 'манго', 'яблука']
Зверніть увагу: коли крок дорівнює 2, ми отримуємо елемен-
ти, що знаходяться на позиціях 0, 2, ... Коли крок дорівнює 3, ми
отримуємо елементи з позицій 0, 3, ... і т. д.
Послідовності чудові тим, що дають можливість звертатися
до кортежів, списків і рядків одним способом!

1.11.5. Множина

Множина – це невпорядковані набори простих об'єктів. Вони


необхідні тоді, коли присутність об'єкта в наборі важливіша за
порядок або те, скільки разів об'єкт там зустрічається.
Використовуючи множину, можна здійснювати перевірку
приналежності, визначати, чи є дана множина підмножиною
іншої множини, знаходити перетин множин тощо.
>>> bri = set(['Бразилія', 'Росія, 'Індія'])
>>> 'Індія' in bri
True
>>> 'США' in bri
96
False
>>> bric = bri.copy()
>>> bric.add('Китай')
>>> bric.issuperset(bri)
True
>>> bri.remove('Росія')
>>> bri & bric # OR bri.intersection(bric)
{'Бразилія', 'Індія'}
Як це працює. Наведений приклад досить наочний, оскільки ви-
користовує основи теорії множин зі шкільного курсу математики.

1.11.6. Перетворення послідовностей

Перетворення за допомогою функції dict (). Ви можете ви-


користовувати функцію dict (), щоб перетворювати послідовнос-
ті з двох значень на словники. Перший елемент кожної послідо-
вності застосовується як ключ, а другий – як значення.
Для початку розглянемо невеликий приклад, що використовує
список, який містить списки, що складаються із двох елементів:
>>> lol = [ ['a', 'b'], ['c', 'd'], ['e', 'f'] ]
>>> dict(lol)
{'a': 'b', 'c': 'd', 'e': 'f'}
Можна використовувати будь-яку послідовність, що містить
послідовності, які складаються із двох елементів. Розглянемо
інші приклади.
Список, що містить двоелементні кортежі:
>>> lot = [ ('a', 'b'), ('c', 'd'), ('e', 'f') ]
>>> dict(lot)
{'a': 'b', 'c': 'd', 'e': 'f'}
Кортеж, що включає двоелементні списки:
>>> tol = (['a', 'b'], ['c', 'd'], ['e', 'f'])
>>> dict(tol)
{'a': 'b', 'c': 'd', 'e': 'f'}
Список, що містить двосимвольні рядки:
>>> los = [ 'ab', 'cd', 'ef' ]
97
>>> dict(los)
{'a': 'b', 'c': 'd', 'e': 'f'}
Кортеж, що містить двосимвольні рядки:
>>> tos = ('ab', 'cd', 'ef')
>>> dict(tos)
{'a': 'b', 'c': 'd', 'e': 'f'}
Ітерування за кількома послідовностями за допомогою
функції zip (). Існує корисний прийом – паралельне ітерування
за кількома послідовностями за допомогою функції zip ():
>>> days = ['Monday', 'Tuesday', 'Wednesday']
>>> fruits = ['banana', 'orange', 'peach']
>>> drinks = ['coffee', 'tea', 'beer']
>>> desserts = ['tiramisu', 'ice cream', 'pie', 'pudding']
>>> for day, fruit, drink, dessert in zip(days, fruits, drinks,
desserts):
... print(day, ": drink", drink, "eat", fruit, "enjoy", dessert)
...
Monday : drink coffee – eat banana – enjoy tiramisu
Tuesday : drink tea – eat orange – enjoy ice cream
Wednesday : drink beer – eat peach – enjoy pie
Функція zip () припиняє роботу, коли виконується найкоротша
послідовність. Один зі списків (desserts) виявився довшим за інші,
тому результату не буде, поки ми не збільшимо інші списки.
Перед цим було показано, як за допомогою функції dict () можна
створювати словники з послідовностей, що містять два елементи, на
зразок кортежів, списків або рядків. Ви можете використовувати
функцію zip (), щоб пройти кількома послідовностями і створити
кортежі з елементів з однаковими зсувами. Створимо два кортежі з
відповідних одне до одного англійських та українських слів:
>>> english = 'Monday', 'Tuesday', 'Wednesday'
>>> ukraine = 'Понеділок', 'Вівторок', 'Середа'
Тепер використаємо функцію zip (), щоб об'єднати ці кортежі
в пару. Значення, що повертається функцією zip (), саме по собі
не є списком або кортежем, але його можна перетворити на
будь-яку з таких послідовностей:

98
>>> list(zip(english, ukraine))
[('Monday', 'Понеділок'), ('Tuesday', 'Вівторок'), ('Wednesday',
'Середа')]
Передайте результат роботи функції zip () безпосередньо фу-
нкції dict () – і отримаєте невеликий англо-український словник:
>>> dict(zip(english, ukraine))
{'Monday': 'Понеділок', 'Tuesday': 'Вівторок', 'Wednesday': 'Середа'}
Генерування числових послідовностей за допомогою фу-
нкції range (). Функція range () повертає потік чисел у заданому
діапазоні без необхідності створювати і зберігати велику струк-
туру даних на зразок списку або кортежу. Це дозволяє створю-
вати великі діапазони, не використавши всю пам'ять комп'ютера
і не "обрушивши" програму:
range(start, stop, step)
Якщо опустити значення start, то діапазон почнеться з 0. Необ-
хідним є лише значення stop: воно визначає останнє значення, яке
буде створене безпосередньо перед зупинкою функції. Значення за
умовчанням step дорівнює 1, але ви можете змінити його на -1.
Як і zip (), функція range () повертає ітераційний об'єкт, тому
необхідно пройти за значеннями за допомогою конструкції for ...
in або перетворити об'єкт на послідовність на зразок списку.
Створимо діапазон 0, 1, 2:
>>> for x in range(0,3):
... print(x)
...
0
1
2
>>> list(range(0, 3))
[0, 1, 2]
Так можна створити діапазон від 2 до 0:
>>> for x in range(2, -1, -1):
... print(x)...
2
1

99
0
>>> list(range(2, -1, -1))
[2, 1, 0]
Включення. Включення – це компактний спосіб створити
структуру даних із одного або більше ітераційних об'єктів.
Включення дозволяють об'єднувати цикли й умовні перевірки,
не використовуючи громіздкий синтаксис. Іншими словами, це
одна з характерних особливостей Python.
Включення списків. Можна створити список цілих чисел від 1
до 5, додаючи їх туди по одному за раз, наприклад:
>>> number_list = []
>>> number_list.append(1)
>>> number_list.append(2)
>>> number_list.append(3)
>>> number_list.append(4)
>>> number_list.append(5)
>>> number_list
[1, 2, 3, 4, 5]
Можна також використовувати ітераційний елемент і функ-
цію range ():
>>> number_list = []
>>> for number in range(1, 6):
... number_list.append(number)
...
>>> number_list
[1, 2, 3, 4, 5]
або перетворити на список результат роботи функції range ():
>>> number_list = list(range(1, 6))
>>> number_list
[1, 2, 3, 4, 5]
Усі ці підходи абсолютно коректні в Python і генерують од-
наковий результат. Однак характернішим для Python є створен-
ня списку за допомогою включення списку. Найпростіша форма
такого включення має вигляд:
[Вираз for елемент in ітераційний об'єкт]

100
Включення списку цілих чисел має такий вигляд:
>>> number_list = [number for number in range(1,6)]
>>> number_list
[1, 2, 3, 4, 5]
У першому рядку потрібно, щоб перша змінна number сфор-
мувала значення для списку: слід розмістити результат роботи
циклу в змінній number_list. Друга змінна number є частиною
циклу for. Щоб показати, що перша змінна number є виразом,
спробуємо такий варіант:
>>> number_list = [number-1 for number in range(1,6)]
>>> number_list
[0, 1, 2, 3, 4]
Включення списку переміщує цикл у квадратні дужки. Роз-
глянутий приклад включення ненабагато простіший за попере-
дній, але це ще не все. Включення списку може містити умов-
ний вираз, який має приблизно такий вигляд:
[Вираз for елемент in ітераційний об'єкт if умова]
Зробимо нове включення, яке створює список, що складаєть-
ся тільки з парних чисел, розташованих у діапазоні від 1 до 5
(пам'ятайте, що вираз number% 2 має значення True для парних
чисел і False – для непарних):
>>>a_list = [number for number in range(1,6) if number %
2 == 1]
>>> a_list
[1, 3, 5]
Тепер включення має трохи компактніший вигляд порівняно
з його традиційним аналогом:
>>> a_list = []
>>> for number in range(1,6):
... if number % 2 == 1:
... a_list.append(number)
...
>>> a_list
[1, 3, 5]
.

101
Нарешті так само, як у разі вкладених циклів, можна написа-
ти більш ніж один набір операторів for... для відповідного виве-
дення даних. Щоб продемонструвати це, спочатку створимо
вкладений цикл і виведемо на екран результат:
>>> rows = range(1,4)
>>> cols = range(1,3)
>>> for row in rows:
... for col in cols:
... print(row, col)
...
11
12
21
22
31
32
Тепер скористаємося включенням і дамо його змінній cells,
створюючи тим самим список кортежів (row, col):
>>> rows = range(1,4)
>>> cols = range(1,3)
>>> cells = [(row, col) for row in rows for col in cols]
>>> for cell in cells:
... print(cell)
...
(1, 1)
(1, 2)
(2, 1)
(2, 2)
(3, 1)
(3, 2)
Включення словника. Для словників також можна створити
включення. Найпростіша його форма має звичний вигляд:
{Вираз_ключа: вираз_значення for вираз in ітераційний об'єкт}
Як і у випадку зі включеннями списку, виділення словників
також мають перевірки if і кілька операторів for:
>>> word = 'letters'
102
>>> letter_counts = {letter: word.count(letter) for letter in word}
>>> letter_counts
{'l': 1, 'e': 2, 't': 2, 'r': 1, 's': 1}
Ми запускаємо цикл, проходячи по кожній із семи літер у ря-
дку letters, і рахуємо, скільки разів з'являється ця літера. Два
наші виклики word.count (letter) – лише марне витрачання часу,
оскільки нам треба підрахувати літери "e" та "t" два рази. Однак
коли ми рахуємо літеру "e" удруге, то не заподіюємо шкоди,
оскільки лише замінюємо вже існуючий запис у словнику; те
саме стосується підрахунку літер "t". Такий спосіб розв'язання
задачі більш характерний для Python:
>>> word = 'letters'
>>> letter_counts = {letter: word.count(letter) for letter in
set(word)}
>>> letter_counts
{'t': 2, 'l': 1, 'e': 2, 'r': 1, 's': 1}
Ключі словника розташовуються в іншому, ніж у попере-
дньому прикладі, порядку, оскільки ітерація по результату робо-
ти функції set (word) повертає літери в іншому порядку, ніж
ітерація по рядку word.
Включення множини. Найпростіша версія має вигляд вклю-
чення списку або словника, які ви щойно бачили:
{Вираз for вираз in ітераційний об'єкт}
Довші версії (перевірки if, множинні оператори for) також
доступні для множин:
>>> a_set = {number for number in range(1,6) if number % 3 == 1}
>>> a_set
{1, 4}
Включення генератора. У Python генератор – це об'єкт, який
призначений для створення послідовностей. Застосування генера-
торів – один зі способів надати дані ітераційному об'єкту. Можна
виконувати ітерацію безпосередньо по цьому об'єкту генератора:
>>> for number in number_thing:
... print(number)
...

103
1
2
3
4
5
Можна також обернути виклик list () навколо включення ге-
нератора, щоб змусити його працювати як включення списку:
>>> number_list = list(number_thing)
>>> number_list
[1, 2, 3, 4, 5]
Вправи
1. Прогресія. Дано ціле число N (> 1), а також перший член A
і різниця D арифметичної прогресії (кількість символів у прі-
звищі та імені студента, відповідно). Сформувати й вивести ма-
сив розміром N, що містить N перших членів такої прогресії:
A, A + D, A + 2 × D, A + 3 × D,. . . .
2. Масив сум попередніх членів. Дано цілі числа N (> 2), A та
B (кількість символів у прізвищі та імені студента, відповідно).
Сформувати й вивести цілочисловий масив розміром N, перший
елемент якого дорівнює A, другий – B, а кожний наступний –
сумі всіх попередніх.
3. Формування масиву із середньоарифметичного елементів
іншого масиву. Дано масив A розміром N. Сформувати новий
масив B того самого розміру за таким правилом: елемент BK
дорівнює середньому арифметичному елементів масиву A з но-
мерами від 1 до K.
4. Об'єднання двох масивів. Дано два масиви A та B розміром
5, елементи яких упорядковані за зростанням. Об'єднати масиви
так, щоб результуючий масив C (розміром 10) залишився впоря-
дкованим за зростанням.
5. Об'єднання трьох масивів. Дано три цілочислові масиви A,
B та C розміром NA, NB, NC, відповідно, елементи яких упоряд-
ковані за спаданням. Об'єднати масиви так, щоб результуючий
цілочисловий масив D (розміром NA + NB + NC) залишився впо-
рядкованим за спаданням.
6. Сортування масиву. Дано масив розміром N. Упорядкувати йо-
го елементи за зростанням. Вбудовані функції не використовувати.

104
1.12. Робота з файлами
Відкривати й використовувати файли для читання або запису
можна шляхом створення об'єкта класу file, а читати/записувати
у файл – за допомогою його методів read, readline або write, від-
повідно. Можливість читати або записувати у файл залежить від
режиму, зазначеного при відкривання файлу. Після закінчення
роботи з файлом треба викликати метод close, щоб указати
Python, що файл більше не використовується.
poem = '''\
Програмувати весело.
Якщо робота нудна,
Щоб надати їй веселий тон –
використовуй Python!
'''
f = open('poem.txt', 'w') # відкриваємо для запису(writing)
f.write(poem) # записуємо текст у файл
f.close() # закриваємо файл
f = open('poem.txt') # якщо не вказано режим, за умовчанням
# мається на увазі режим читання('r'eading)
while True:
line = f.readline()
if len(line) == 0: # Нульова довжина – кінець файлу(EOF)
break
print(line, end='')
f.close() # закриваємо файл
Виведення:
Програмувати весело.
Якщо робота нудна,
щоб надати їй веселий тон –
використовуй Python!
Як це працює. Спочатку ми відкриваємо файл за допомогою
вбудованої функції open із зазначенням імені файлу та режиму,
у якому хочемо його відкрити. Режим може бути для читання

105
('r'), запису ('w') або додавання ('a'). Можна також указати, у яко-
му вигляді ми будемо зчитувати, записувати або додавати дані:
текстовому ('t') або бінарному ('b'). Насправді існують багато ін-
ших режимів, і help (open) дасть їх детальний опис. За умовчан-
ням open () відкриває файл як текст у режимі для читання.
У нашому прикладі ми спочатку відкриваємо файл у режимі за-
пису тексту і використовуємо метод write файлового об'єкта для
запису у файл, після чого закриваємо файл за допомогою close.
Далі відкриваємо той самий файл для читання. У цьому ви-
падку немає потреби вказувати режим, оскільки режим "читання
текстового файлу" застосовується за умовчанням. Ми зчитуємо
файл методом readline у циклі. Цей метод повертає повний ря-
док, включаючи символ переведення рядка в кінці. Коли ж він
повертає порожній рядок, це означає, що ми досягли кінця фай-
лу; тоді перериваємо цикл за допомогою break.
За умовчанням функція print () виводить текст, автоматично
додаючи символ переведення рядка в кінці. Ми ігноруємо цей
символ, указуючи end = '', оскільки рядки й без того закінчують-
ся символом переведення рядка. І, нарешті, закриваємо файл за
допомогою close.
Тепер перевіряємо зміст файлу poem.txt, щоб переконатися,
що програма дійсно записала текст у нього і зчитувала з нього.

1.12.1. Структуровані текстові файли. CSV24

Файл у форматі CSV – універсальний засіб для перенесення


табличної інформації між додатками (електронними таблицями,
СУБД, адресними книгами тощо).
Для початку розглянемо, як читати й записувати список ряд-
ків, кожний з яких містить список колонок:
>>> import csv
>>> villains = [
... ['Doctor', 'No'],
... ['Rosa', 'Klebb'],

24
CSV (Comma Separated Values) – змінні, розділені комами.
106
... ['Mister', 'Big'],
... ['Auric', 'Goldfinger'],
['Ernst', 'Blofeld'],
... ]
>>> with open('villains', 'wt') as fout: # менеджер контексту
... csvout = csv.writer(fout)
... csvout.writerows(villains)
Виведення:
Doctor,No
Rosa,Klebb
Mister,Big
Auric,Goldfinger
Ernst,Blofeld
Конструкція with ... as використовується для гарантії того, що
критичні функції виконаються в будь-якому випадку. Найпоши-
реніший приклад використання цієї конструкції – відкривання
файлів, оскільки вона зручна і гарантує закривання файлу в
будь-якому випадку.
Тепер спробуємо зворотне зчитування:
>>> import csv
>>> with open('villains', 'rt') as fin: # менеджер контексту
... cin = csv.reader(fin)
... villains = [row for row in cin] # використання включення
списку
...
>>> print(villains)
[['Doctor', 'No'], ['Rosa', 'Klebb'], ['Mister', 'Big'],
['Auric', 'Goldfinger'], ['Ernst', 'Blofeld']]
Ми скористалися структурою, породженою функцією reader
(). Вона створила в об'єкті cin рядки, які ми можемо зчитати за
допомогою циклу for.
Використовуючи функції reader () і writer () з їхніми стандар-
тними опціями, отримаємо колонки, розділені комами, і рядки,
розділені символами переведення рядка.

107
Дані можуть мати формат списку словників, а не списку спи-
сків. Знову зчитаємо файл villains, цього разу використовуючи
нову функцію DictReader () і вказуючи імена колонок:
>>> import csv
>>> with open('villains', 'rt') as fin:
... cin = csv.DictReader(fin, fieldnames=['first', 'last'])
... villains = [row for row in cin]
...
>>> print(villains)
[{'last': 'No', 'first': 'Doctor'},
{'last': 'Klebb', 'first': 'Rosa'},
{'last': 'Big', 'first': 'Mister'},
{'last': 'Goldfinger', 'first': 'Auric'},
{'last': 'Blofeld', 'first': 'Ernst'}]

1.12.2. Модуль pickle

Рядки легко можуть бути записані у файл і зчитані з нього.


Числа вимагають додаткових невеликих зусиль, оскільки метод
read () завжди повертає рядки, які треба обробити, наприклад, за
допомогою функції int (). Однак задача сильно утруднюється,
якщо необхідно зберігати складніші типи даних, такі як списки,
словники чи реалізації класів.
Щоб користувачеві не доводилося постійно писати й налашто-
вувати код для збереження складних типів даних, Python надає
стандартний модуль pickle. Модуль pickle дозволяє отримати
зображення майже будь-якого об'єкта (навіть деякі форми коду) у
вигляді набору байтів (рядків), однакового для всіх платформ.
Цей процес називають консервуванням (pickling). Таке зображен-
ня (законсервований об'єкт) можна зберегти у файлі або передати
через мережне з'єднання на іншу машину. До зчитаного файлу
або прийнятого на іншій машині законсервованого об'єкта може
бути застосована операція відновлення (unpickling).
Якщо є об'єкт x і файловий об'єкт f, відкритий для запису, то най-
простіший спосіб зберегти об'єкт потребує лише одного рядка коду:
pickle.dump(x, f)

108
Так само просто можна відновити об'єкт (f – файловий об'єкт,
відкритий для читання):
x = pickle.load(f)
Використання модуля pickle є стандартним у мові Python
шляхом збереження тривалих за часом (persistent) об'єктів для
подальшого використання. Модуль настільки часто використо-
вується, що багато авторів додаткових модулів піклуються про
те, щоб нові типи даних (наприклад матриці) могли бути прави-
льно законсервовані.
import pickle
# ім'я файлу, у якому зберігається об'єкт
shoplistfile = 'shoplist.data'
# список покупок
shoplist = ['яблука', 'манго', 'морква']
# Запис у файл
f = open(shoplistfile, 'wb')
pickle.dump(shoplist, f) # розміщуємо об'єкт у файлі
f.close()
del shoplist # знищуємо змінну shoplist
# Зчитуємо зі сховища
f = open(shoplistfile, 'rb')
storedlist = pickle.load(f) # завантажуємо об'єкт із файлу
print(storedlist)
Виведення:
['яблука', 'манго', 'морква']
Як це працює. Щоб зберегти об'єкт у файлі, треба спочатку
відкрити файл за допомогою open у режимі бінарного запису
('wb'), після чого викликати функцію dump із модуля pickle. Цей
процес називається консервацією (pickling).
Потім зчитуємо об'єкт за допомогою функції load з модуля
pickle. Цей процес називається розконсервуванням (unpickling).
Вправи
1. Створення файлу арифметичної прогресії. Дано ім'я файлу і
дійсні числа A та D. Створити файл дійсних чисел з такою назвою і
записати в нього 10 перших членів арифметичної прогресії з почат-
ковим членом A та різницею D: A, A + D, A + 2 × D, A + 3 × D,. . . .
109
2. Створення файлу на основі даних іншого файлу. Дано
файл цілих чисел. Створити новий файл, який містить ті самі
елементи, що й вихідний файл, але у зворотному порядку.
3. Перетворення елементів файлу цілих чисел. Дано файл ці-
лих чисел з елементами A1, A2,. . ., AN (N – кількість елементів у
файлі). Замінити вихідне розташування його елементів на таке:
A1, AN, A2, AN-1, A3,. . . .
4. Заміна змісту файлів. Дано два файли довільного типу.
Поміняти місцями їхні змісти.
5. Створення файлу з даних інших файлів. Дано три файли
цілих чисел однакового розміру з іменами SA, SB, СК і рядок SD.
Створити новий файл з ім'ям SD, у якому чергувалися б елемен-
ти вихідних файлів з тим самим номером:
A1, B1, C1, A2, B2, C2,. . . .
6. Створення файлу транспонованої матриці. Дано файл дійс-
них чисел, що містить елементи квадратної матриці (по рядках).
Створити новий файл, який містить елементи матриці, транспо-
нованої до вихідної.
7. Створення файлу суми матриць. Дано два файли дійсних
чисел з іменами SA та SB, що містять елементи квадратних мат-
риць A та B (по рядках). Створити нові файли: з ім'ям SC1, що
містить елементи суми A + B; SC2, що містить елементи різниці
A – B; SC3, що містить елементи добутку A × B.

1.13. Обробка виняткових ситуацій


Можна виділити (як мінімум) два типи помилок: син-
таксичні помилки та винятки. Синтаксичні помилки також є
винятками, які, однак, не можуть бути перехоплені на тому
самому рівні.
Винятки виникають тоді, коли в програмі створюється деяка
виняткова ситуація. Наприклад, до чого призведе спроба читан-
ня неіснуючого файлу? А якщо файл був випадково видале-
ний, поки програма працювала? Такі ситуації обробляються за
допомогою винятків. Це стосується і програм, що містять недій-
сні команди.
110
1.13.1. Помилки

Розглянемо простий виклик функції print. Що буде, якщо ми


помилково напишемо print як Print? Зверніть увагу на велику
літеру. У цьому випадку Python виявляє синтаксичну помилку.
>>> Print('Привіт, Світ!')
Traceback (most recent call last):
File "<pyshell#0>", line 1, in <module>
Print('Привіт, Світ!')
NameError: name 'Print' is not defined
>>> print('Привіт, Світ!')
Привіт, Світ!
Зверніть увагу, що була виявлена помилка NameError, а та-
кож указане місце, де вона була виявлена. Так у даному випадку
діє обробник помилок.

1.13.2. Виняток

Спробуємо зчитати що-небудь від користувача. Натисніть


Сtrl-D і подивіться, що станеться.
>>> s = input('Введіть що-небудь –> ')
Введіть що-небудь –>
Traceback (most recent call last):
File "<pyshell#2>", line 1, in <module> s = input('Введіть
що-небудь –> ')
EOFError: EOF when reading a line
Python виявляє помилку з ім'ям EOFError; це означає, що він
виявив символ кінця файлу (який вводиться за допомогою Ctrl-
D) там, де його не мало б бути.

1.13.3. Обробка винятків

Обробляти виняток можна за допомогою оператора


try..except. При цьому всі звичайні команди поміщаються всере-
дину try-блоку, а всі обробники винятків – в except-блок.
111
Приклад:
try:
text = input(Введіть що-небудь –> ')
except EOFError:
print('Ну навіщо ви зробили мені EOF?')
except KeyboardInterrupt:
print('Ви відмінили операцію.')
else:
print('Ви ввели {0}'.format(text))
Виведення:
Введіть що-небудь –> # натисніть ctrl-d
'Ну навіщо ви зробили мені EOF?
Введіть що-небудь –> # натисніть ctrl-c
Ви відмінили операцію.
Введіть що-небудь –> без помилок
Ви ввели без помилок
Як це працює. Тут ми помістили всі команди, які можуть ви-
кликати виняток/помилку, усередину блоку try, а обробники
відповідних помилок/винятків – у блок except. Вираз except мо-
же обробляти як одиничну помилку або виняток, так і список
помилок/винятків у дужках. Якщо не вказане ім'я помилки або
винятку, то оброблятися будуть усі помилки та винятки.
Пам'ятайте, що для кожного виразу try має бути хоча б один
відповідний вираз except.
Якщо помилку або виняток оброблено, то буде викликаний
оброблювач Python за умовчанням, який зупиняє виконання
програми й виводить на екран повідомлення про помилку. Вище
ми це вже бачили.
Можна також додати пункт else до відповідного блоку
try..except. Цей пункт виконується тоді, коли немає винятків.
Інструкція try працює таким чином.
Спочатку виконується гілка try (інструкції, що містяться між
ключовими словами try та except).
• Якщо не виникає виняткова ситуація, то гілка except пропу-
скається і виконання інструкції try завершується.

112
• Якщо під час виконання гілки try генерується виняток, то
решта гілки пропускається. Далі, якщо тип (клас) винятку відпо-
відає зазначеному після ключового слова except, то виконується
гілка except і виконання інструкції try завершується.
• Якщо виняток не відповідає зазначеному після ключового слова
except типу помилки, то він передається зовнішньому блоку try або,
якщо обробник не знайдений, вважається неперехопленим. Тоді
виконання переривається і виводиться повідомлення про помилку.
Інструкція try може мати більше однієї гілки except, визна-
чаючи обробники для різних винятків. Виконуватися буде тіль-
ки один з них. Обробляються тільки винятки, що генеровані у
відповідній гілці try, але не в інших обробниках інструкції try.
Після ключового слова except може бути зазначено кілька типів
винятків у вигляді кортежу:
... except (RuntimeError, TypeError, NameError):
... pass
Після всіх гілок except інструкція try може містити гілку else,
яка буде виконуватися у випадку, якщо під час виконання гілки
try винятки не генеруються.
Виняток може мати асоційоване з ним значення – аргумент,
переданий класу винятків при ініціалізації. Наявність і тип ар-
гументу залежать від типу винятку. Асоційоване значення вико-
ристовується при отриманні для винятку рядкового значення.
Щоб отримати значення винятку, у гілці except після класу ви-
нятків (або кортежу класів) укажіть змінну:
>>> try:
... spam()
... except NameError, x:
... print “Ім'я”, x, “не визначено”
...
Ім'я spam не визначене
Якщо виняток не обробляється, то його значення виводиться
в повідомленні про помилку після імені класу винятків.
Оброблювач перехоплює не тільки винятки, що згенеровані
безпосередньо в блоці try, а й ті, що згенеровані у функціях, які
з нього викликаються. Наприклад:
>>> def this_fails():
... x = 1/0
113
...
>>> try:
... this_fails()
... except ZeroDivisionError, exc:
... print ('Помилка часу виконання:', exc)
...
Помилка часу виконання: integer division or modul

1.13.4. Виклик винятку


Виняток можна викликати за допомогою оператора raise, пе-
редавши йому ім'я помилки/винятку, а також об'єкт винятку,
який треба відстежити.
Помилка або виняток, що викликаються, мають бути класом,
який прямо або непрямо є похідним від класу Exception.
Приклад:
class ShortInputException(Exception):
'''Клас винятку, визначений користувачем.'''
def __init__(self, length, atleast):
Exception.__init__(self)
self.length = length
self.atleast = atleast
try:
text = input('Введіть що-небудь –> ')
if len(text) < 3:
raise ShortInputException(len(text), 3)
# Тут може виконуватися звичайна робота
except ShortInputException as ex:
print('ShortInputException: Довжина введеного рядка – {0}; \
очікувалось як мінімум {1}'.format(ex.length, ex.atleast))
else:
print('Винятків не було.')
Виведення:
Введіть що-небудь –> аShortInputException: Довжина введе-
ного рядка – 1; очікувалось як мінімум 3
Введіть що-небудь –> абв
Виключень не було.
114
Як це працює. Створимо власний тип винятку, який назива-
ється ShortInputException. Він містить два поля: length, що збері-
гає довжину введеного тексту, і atleast, яке вказує, яку мінімаль-
ну довжину тексту очікувала програма.
У пункті except ми вказуємо клас помилки ShortInput
Exception, що буде збережений як змінна ex, яка містить відпо-
відний об'єкт помилки/винятку. Це аналогічно параметрам і
аргументам при виконанні функції. Усередині пункту except ми
використовуємо поля length та atleast об'єкта винятку для виве-
дення необхідних повідомлень користувачеві.

1.13.5. Try .. Finally

Уявімо, що програма зчитує файл. Необхідно переконатися,


що об'єкт файлу був коректно закритий і не виникало жодного
винятку. Цього можна досягти із застосуванням блоку finally.
import time
try:
f = open('poem.txt')
while True: # звичайний спосіб читання файлу
line = f.readline()
if len(line) == 0:
break
print(line, end='')
time.sleep(2) # очікування на деякий час
except KeyboardInterrupt:
print('!! Ви скасували читання файлу.')
finally:
f.close()
print('(Очищення: Закриття файлу)')
Виведення:
Програмувати весело
Якщо робота нудна,
Щоб надати їй веселий тон –
!! Ви скасували читання файлу.
(Очищення: Закриття файлу)
115
Як це працює. Тут ми здійснюємо звичайні операції читання з
файлу, але в даному випадку додаємо двосекундний сон після
виведення кожного рядка за допомогою функції time.sleep, щоб
програма виконувалася повільно (адже Python дуже швидкий).
Під час виконання програми натисніть ctrl-c, щоб перерва-
ти/скасувати її виконання.
Поспостерігайте, як при цьому видається виняток KeyboardInterrupt
і програма закінчує свою роботу. Однак, перш ніж програма закін-
чить роботу, виконається пункт finally, і файловий об'єкт буде завжди
закритий. Гілка finally виконується незалежно від того, чи є винятко-
ва ситуація під час виконання блоку try, у тому числі якщо закінчення
роботи програми відбувається за інструкцією break або return.
Інструкція try може мати одну або кілька гілок except, або
одну гілку finally, але не обидва варіанти одночасно.

1.13.6. Оператор with

Типовою схемою використання оператора є запит деякого ре-


сурсу в блоці try з подальшим його звільненням у блоці finally.
Для того, щоб зробити це коректніше, існує оператор with:
with open("poem.txt") as f:
for line in f:
print(line, end='')
Як це працює. Виведення має бути таким самим, як і в попе-
редньому прикладі. Різниця лише в тому, що тут ми використо-
вуємо функцію open з оператором with. Таким чином, ми зали-
шаємо автоматичне закриття файлу під відповідальність опера-
тора функції with open.
При цьому відбувається таке. Є певний протокол, який вико-
ристовується оператором with. Він зчитує об'єкт, що повертаєть-
ся оператором open. Назвемо його в нашому випадку "the_file".
Перед запуском блоку коду, що міститься в ньому, оператор
with завжди викликає функцію the_file.__enter__, а також
the_file.__exit__ – після завершення виконання цього блоку коду.
Отже, код, який ми б написали в блоці finally, буде автомати-
чно оброблений методом __exit__. Це позбавляє нас необхіднос-
ті повторно у явному вигляді вказувати оператори try..finally.
116
Вправи
1. Введення двох значень. Напишіть програму, яка запитує
введення двох значень. Якщо хоча б одне з них не є числом, то
має виконуватися конкатенація рядків. В інших випадках введе-
ні числа підсумовуються.
Виконайте обробку можливих виключень.
2. Перевірка парності числа. Напишіть програму, яка переві-
ряє парність числа, що вводиться із клавіатури. Виконайте об-
робку можливих винятків.
3. Створення матриці. Напишіть програму, яка буде генеру-
вати матрицю з випадкових цілих чисел. Користувач указує ро-
змірність матриці та діапазон цілих чисел. Виконайте обробку
помилок введення користувача.

1.14. Визначення
та використання класів
Два основні аспекти об'єктно-орієнтованого програмування –
класи та об'єкти. Клас створює новий тип, а об'єкти є екземпля-
рами класу. Коли ми говоримо про змінні типу int, це означає,
що змінні, які зберігають цілочислові значення, є екземплярами
(об'єктами) класу int.
Об'єкти можуть зберігати дані у звичайних змінних, що
належать об'єкту. Змінні, що належать об'єкту або класу, на-
зиваються полями. Об'єкти також можуть мати деякий функ-
ціонал, тобто функції, що належать класу. Такі функції при-
йнято називати методами класу. Ця термінологія важлива,
оскільки допомагає відрізняти незалежні функції та змінні від
тих, що належать класу або об'єкту. Усе разом (поля і методи)
називають атрибутами класу.
Поля бувають двох типів: вони можуть належати кожному
окремому екземпляру об'єкта класу або всьому класу і нази-
ваються змінними екземпляра або змінними класу, відповідно.
Клас створюється ключовим словом class. Поля і методи кла-
су записуються у блоці коду з відступом.

117
1.14.1. Класи

Найпростіший клас показаний у такому коді:


class Person:
pass # Порожній блок
p = Person()
print(p)
Виведення:
<__main__.Person object at 0x019F85F0>
Як це працює. Ми створюємо новий клас за допомогою операто-
ра class та імені класу. Далі йде блок виразів, які формують тіло
класу. У даному випадку блок порожній, на що вказує оператор pass.
Далі створюємо об'єкт-екземпляр класу, записуючи ім'я класу
з дужками. Для перевірки з'ясовуємо тип змінної, просто виво-
дячи її на екран. Бачимо, що в нас є екземпляр класу Person у
модулі __main__.
Зверніть увагу, що виводиться також адреса в пам'яті комп'ю-
тера, де зберігається об'єкт. На вашому комп'ютері адреса буде
іншою, оскільки Python зберігає об'єкти там, де є вільне місце.

1.14.2. Методи об'єктів

Отже, ми з'ясували, що класи/об'єкти можуть мати методи,


які є функціями, за винятком додаткової змінної self.
class Person:
def sayHi(self):
print('Привіт! Як справи?')
p = Person()
p.sayHi()
# Цей короткий приклад можна також записати як
Person().sayHi()
Виведення:
Привіт! Як справи?
Як це працює. Тут ми бачимо, як діє self. Зверніть увагу, що ме-
тод sayHi не приймає параметрів, але має self у визначенні функції.
118
1.14.3. Метод __init__

Існує багато методів, які відіграють особливу роль у класах


Python. Метод __init__ запускається, як тільки об'єкт класу реа-
лізується. Метод корисний для здійснення ініціалізації, необхід-
ної для об'єкта. Зверніть увагу на подвійні підкреслення на по-
чатку й у кінці імені.
Приклад:
class Person:
def __init__(self, name):
self.name = name
def sayHi(self):
print('Привіт! Мене звуть', self.name)
p = Person('Swaroop')
p.sayHi()
# Цей короткий приклад можна також записати як
Person('Swaroop').sayHi()
Виведення:
Привіт! Мене звуть Swaroop
Як це працює. Тут ми визначаємо метод __init__ так, щоб
він приймав параметр name (поряд зі звичайним self). Далі
створюємо нове поле з ім'ям name. Зверніть увагу, що дві різ-
ні змінні мають назву name. Це не є проблемою, оскільки точ-
ка у виразі self.name позначає, що існує щось із ім'ям name,
що є частиною об'єкта self, а інше name – локальна змінна.
Оскільки ми у явному вигляді вказуємо, до якого імені звер-
таємося, то плутанини не виникає.
Важливо зазначити, що при створенні нового екземпляра
класу ми не викликаємо метод __init__ явно, а передаємо ар-
гументи в дужках після імені класу. У цьому й полягає спеці-
альна роль методу.
Далі ми можемо використовувати поле self.name у своїх ме-
тодах, що продемонстровано в методі sayHi.

119
1.14.4. Змінні класу та об'єкта

Функціональну частину класів і об'єктів (тобто методи) ми об-


говорили, тепер ознайомимося із частиною даних. Дані, тобто
поля, є не чим іншим, як звичайними змінними, що зв'язані в про-
сторі імен класів і об'єктів. Це означає, що їхні імена дійсні тільки
в контексті цих класів або об'єктів. Звідси й назва – простір імен.
Існують два типи полів: змінні класу і змінні об'єкта, які різ-
няться залежно від того, належить змінна класу або об'єкту.
Змінні класу є загальнодоступними, тобто доступ до них мо-
жуть отримувати всі екземпляри цього класу. Змінна класу одна,
тому коли будь-який з об'єктів її змінює, така зміна відображу-
ється в усіх інших екземплярах цього класу.
Змінні об'єкта належать кожному окремому екземпляру класу.
У цьому випадку в кожного об'єкта є власна копія поля, тобто не
колективна і жодним чином не зв'язана з іншими такими самими
полями в інших екземплярах. Це легко зрозуміти на прикладі:
class Robot:
'''Зображує робота з ім'ям.'''
# Змінна класу, що містить кількість роботів
population = 0
def __init__(self, name):
'''Ініціалізація даних.'''
self.name = name
print('(Ініціалізація {0})'.format(self.name))
# При створенні цієї особи робот додається
# до змінної 'population'
Robot.population += 1
def __del__(self):
'''Я вмираю.'''
print('{0} знищується!'.format(self.name))
Robot.population -= 1
if Robot.population == 0:
print('{0} був останнім.'.format(self.name))
else:
print('Залишилось {0:d} працюючих робо-
тів.'.format(Robot.population))
120
def sayHi(self):
'''Вітання робота.'''
print('Вітаю! Мої господарі звуть мене
{0}.'.format(self.name))
def howMany():
'''Виводить кількість роботів.'''
print('У нас {0:d} роботів.'.format(Robot.population))
howMany = staticmethod(howMany)
droid1 = Robot('R2-D2')
droid1.sayHi()
Robot.howMany()
droid2 = Robot('C-3PO')
droid2.sayHi()
Robot.howMany()
print("\n Тут роботи можуть виконати якусь роботу.\n")
print("Роботи закінчили свою роботу. Давайте знищимо їх.")
del droid1
del droid2
Robot.howMany()
Виведення:
(Ініціалізація R2-D2)
Вітаю! Мої господарі звуть мене R2-D2.
У нас 1 робот.
(Ініціалізація C-3PO)
Вітаю! Мої господарі звуть мене C-3PO.
У нас 2 роботи.
Тут роботи можуть виконати якусь роботу.
Роботи закінчили свою роботу. Давайте знищимо їх.
R2-D2 знищується!
Залишився 1 працюючий робот
C-3PO знищується!
C-3PO був останнім.
У нас 0 роботів.

121
Як це працює. Наведений приклад допомагає продемонстру-
вати природу змінних класу і об'єкта. Тут population належить
класу Robot, тому є змінною класу. Змінна name належить об'-
єкту (їй присвоюється значення за допомогою self), тому є
змінною об'єкта.
Таким чином, ми звертаємося до змінної класу population як
Robot.population, а не self.population. До змінної ж об'єкта name
у всіх методах цього об'єкта ми звертаємося за допомогою по-
значення self.name. Пам'ятайте про цю просту різницю між
змінними класу та об'єкта. Також майте на увазі, що змінна
об'єкта з тим самим ім'ям, що і змінна класу, зробить останню
недоступною ("сховає").
Метод howMany належить класу, а не об'єкту. Це означає, що
можна визначити його як classmethod або staticmethod, залежно
від того, чи треба нам знати, з яким класом ми працюємо. Оскі-
льки нам не потрібна така інформація, то скористаємося
staticmethod.
Можна досягти того самого результату, використовуючи де-
коратори:
@staticmethod
def howMany():
'''Виводить кількість роботів.'''
print('У нас {0:d} роботів.'.format(Robot.population))
Декоратори можна вважати певним спрощеним способом ви-
клику явного оператора, як ми бачили в цьому прикладі.
Поспостерігайте, як метод __init__ використовується для іні-
ціалізації екземпляра з ім'ям Robot. У цьому методі ми збільшу-
ємо лічильник population на 1, оскільки додаємо ще одного ро-
бота. Зауважте, що значення self.name для кожного об'єкта свої,
що вказує на природу змінних об'єкта.
Пам'ятайте, що до змінних і методів об'єкта треба звертатися,
користуючись тільки self. Це називається доступом до атрибутів.
У наведеному вище прикладі ми спостерігали застосування
рядків документації для класів так само, як для методів. Під час
виконання програми можна звертатися до рядка документації
класу за допомогою Robot.__doc__, а до рядка документації ме-
тоду – за допомогою Robot.sayHi.__doc__.
122
Поряд з методом __init__ існує інший спеціальний метод
__del__, який викликається тоді, коли об'єкт "зникає", тобто
більше не використовується, а зайнята ним пам'ять повертається
операційній системі для інших застосувань. У цьому методі ми
просто зменшуємо лічильник Robot.population на 1.
Заздалегідь невідомо, коли саме "зникне" об'єкт, отже, щоб
побачити явно дію методу __del__, треба скористатися операто-
ром del, що ми і зробили вище.
Примітка для програмістів мовами C++ / Java / C #. У Python
усі члени класу (включаючи дані) є публічними (public), а всі
методи – віртуальними (virtual).
Виняток. Якщо ім'я змінної починається з подвійного підкре-
слення, наприклад __privatevar, то Python робить цю змінну
приватною (private). Тому прийнято ім'я будь-якої змінної, яка
має використовуватися тільки всередині класу або об'єкта, по-
чинати з підкреслення; усі ж інші імена є публічними і можуть
використовуватися в інших класах/об'єктах. Пам'ятайте, що це
лише традиція, і Python зовсім не зобов'язує робити саме так
(крім подвійного підкреслення).

1.14.5. Спадкування

Одна з головних переваг об'єктно-орієнтованого програму-


вання полягає в багаторазовому використанні того самого коду,
а один зі способів цього досягти – за допомогою механізму ус-
падкування. Найлегше уявити спадкування у вигляді відносин
між класами на зразок типу і підтипу.
Уявімо, що нам треба написати програму, яка відстежує ін-
формацію про викладачів і студентів у коледжі. У них є деякі
загальні характеристики: ім'я, вік і адреса. Є також специфічні
характеристики, такі як зарплата, курси та відпустка для викла-
дачів, а також оцінки й оплата за навчання для студентів.
Можна створити для них незалежні класи і працювати з ни-
ми, але тоді додавання будь-якої нової загальної характеристики
вимагатиме додавання її до кожного з цих незалежних класів
окремо, що робить програму незручною.

123
Краще створити загальний клас з ім'ям SchoolMember, а потім
зробити так, щоб класи викладачів і студентів його успадковува-
ли, тобто щоб вони стали підтипами цього типу (класу), після
чого додати будь-які специфічні характеристики до цих підтипів.
У такого підходу є безліч переваг. Якщо ми додає-
мо/змінюємо якусь функціональність у SchoolMember, то це
автоматично відображується в усіх підтипах.
Наприклад, ми можемо ввести нове поле Посвідчення для
викладачів і студентів, просто додавши його до класу
SchoolMember. З іншого боку, зміни в підтипах ніяк не вплива-
ють на інші підтипи. Ще одна перевага полягає в тому, що звер-
татися до об'єкта Викладач або Студент можна як до об'єкта
SchoolMember, що є корисним у деяких випадках, наприклад
для підрахунку кількості осіб у школі. Ситуація, коли підтип
може бути підставлений у будь-яке місце, де очікується батьків-
ський тип, тобто об'єкт вважається екземпляром батьківського
класу, називається поліморфізмом.
Код батьківського класу використовується багаторазово, то-
му немає необхідності копіювати його в усі класи, як довелося б
у разі використання незалежних класів.
Клас SchoolMember в описаній ситуації називають базовим
класом, або надкласом. Класи Teacher і Student називають похід-
ними класами, або підкласами.
Розглянемо тепер цей приклад у вигляді програми.
class SchoolMember:
''' Зображує будь-яку людину в школі.'''
def __init__(self, name, age):
self.name = name
self.age = age
print('(Створено SchoolMember: {0})'.format(self.name))
def tell(self): '''Вивести інформацію.'''
print('Ім'я:"{0}" Вік:"{1}"'.format(self.name, self.age),
end=" ")
class Teacher(SchoolMember):
'''Зображує викладача.'''
def __init__(self, name, age, salary):
SchoolMember.__init__(self, name, age)
124
self.salary = salary
print('(Створено Teacher: {0})'.format(self.name))
def tell(self):
SchoolMember.tell(self)
print('Заробітна платня: "{0:d}"'.format(self.salary))
class Student(SchoolMember):
'''Зображує студента.'''
def __init__(self, name, age, marks):
SchoolMember.__init__(self, name, age)
self.marks = marks
print('(Створено Student: {0})'.format(self.name))
def tell(self):
SchoolMember.tell(self)
print('Оцінки: "{0:d}"'.format(self.marks))
t = Teacher('Mrs. Shrividya', 40, 30000)
s = Student('Swaroop', 25, 75)
print() # друкує порожній рядок
members = [t, s]
for member in members:
member.tell() # працює як для викладача, так і для студента
Виведення:
(Створено SchoolMember: Mrs. Shrividya)
(Створено Teacher: Mrs. Shrividya)
(Створено SchoolMember: Swaroop)
(Створено Student: Swaroop)
Ім'я:"Mrs. Shrividya" Вік:"40" Заробітна платня: "30000"
Ім'я:"Swaroop" Вік:"25" Оцінки: "75"
Як це працює. Щоб скористатися спадкуванням, при визна-
ченні класу треба вказати імена його базових класів у вигляді
кортежу, що розміщується відразу за його ім'ям. Далі ми бачи-
мо, що метод __init__ базового класу викликається явно за до-
помогою змінної self, щоб форматувати частину об'єкта, який
належить до базового класу. Запам'ятайте: Python не викликає
конструктор базового класу автоматично – його необхідно ви-
кликати самостійно у явному вигляді.
125
Ми також бачимо, як можна викликати методи базового кла-
су, випереджаючи запис імені методу ім'ям класу, а потім пере-
даючи змінну self разом з іншими аргументами.
Зверніть увагу, що при виклику методу tell із класу
SchoolMember екземпляри Teacher або Student можна викорис-
товувати як екземпляри SchoolMember.
Зауважте також, що викликається метод tell із підкласу, а не
метод tell із класу SchoolMember. Це можна розуміти так: Python
завжди починає пошук методів у самому класі, що й робить у
даному випадку. Якщо ж він не знаходить метод, то починає
шукати методи, які належать базовим класам по черзі, у поряд-
ку, у якому вони перераховані в кортежі при визначенні класу.
Якщо при спадкуванні перераховано більше одного класу, то
це називається множинним спадкуванням.
Параметр end використовується в методі tell () для того, щоб
новий рядок починався через пробіл після виклику print ().

1.14.6. Метакласи

Так само як класи використовуються для створення об'єктів,


можна використовувати метакласи для створення класів. Мета-
класи існують для зміни або додавання нової поведінки в класи.
Розглянемо приклад. Припустимо, ми хочемо бути впевнені,
що завжди створюємо лише екземпляри підкласів класу
SchoolMember і не створюємо екземпляри його самого. Для цього
можна використовувати абстрактні базові класи, які є лише кон-
цепцією, не призначеною для використання як реальний клас.
Ми можемо оголосити наш клас як абстрактний базовий клас
за допомогою вбудованого метакласу з іменем ABCMeta.
from abc import *
class SchoolMember(metaclass=ABCMeta):
'''Зображує будь-яку людину в школі.'''
def __init__(self, name, age):
self.name = name
self.age = age
print('(Створено SchoolMember: {0})'.format(self.name))
126
@abstractmethod
def tell(self):
'''Вивести інформацію.'''
print('Ім'я:"{0}" Вік:"{1}"'.format(self.name, self.age), end=" ")
class Teacher(SchoolMember):
'''Зображує викладача.'''
def __init__(self, name, age, salary):
SchoolMember.__init__(self, name, age)
self.salary = salary
print('(Створено Teacher: {0})'.format(self.name))
def tell(self):
SchoolMember.tell(self)
print('Заробітна платня: "{0:d}"'.format(self.salary))
class Student(SchoolMember):
'''Зображує студента.'''
def __init__(self, name, age, marks):
SchoolMember.__init__(self, name, age)
self.marks = marks
print('(Створено Student: {0})'.format(self.name))
def tell(self):
SchoolMember.tell(self)
print('Оцінки: "{0:d}"'.format(self.marks))
t = Teacher('Mrs. Shrividya', 40, 30000)
s = Student('Swaroop', 25, 75)
#m = SchoolMember('abc', 10)
# Це зумовить помилку: "TypeError: Can't instantiate abstract class
# SchoolMember with abstract methods tell"
print() # друкує порожній рядок
members = [t, s]
for member in members:
member.tell() # працює як для викладача, так і для студента
Виведення:
(Створено SchoolMember: Mrs. Shrividya)
127
(Створено Teacher: Mrs. Shrividya)
(Створено SchoolMember: Swaroop)
(Створено Student: Swaroop)
Ім'я:"Mrs. Shrividya" Вік:"40" 'Заробітна платня: "30000"
Ім'я:"Swaroop" Вік:"25" Оцінки: "75"
Як це працює. Можна оголосити метод tell класу
SchoolMember абстрактним і, таким чином, автоматично забо-
ронити створювати екземпляри класу SchoolMember; проте мо-
жна працювати з екземплярами Teacher і Student так, начебто
вони – екземпляри SchoolMember, оскільки вони є підкласами.
Вправи
1. Створення тварини.
1.1. Створення класу. Напишіть код, що описує клас Animal:
• додайте атрибут імені тварини;
• додайте метод eat (), що виводить "Ням-ням";
• додайте методи getName () і setName ();
• додайте метод makeNoise (), що виводить "(Name) гово-
рить Гррр";
• додайте конструктор класу Animal, що виводить "Наро-
дилася тварина – Name".
1.2. Основна програма:
• створіть тварину, у момент створення визначте її ім'я;
• дізнайтеся ім'я тварини через виклик методу getName ();
• змініть ім'я тварини через виклик методу setName ();
• викличте eat () і makeNoise () для тварини.
2. Створення класу для роботи з рядковим типом даних.
Створіть клас StringVar для роботи з рядковим типом даних, що
містить методи set () і get (). Метод set () служить для зміни вмі-
сту рядка, get () – для отримання вмісту рядка. Створіть об'єкт
типу StringVar і протестуйте його методи.
3. Створення класу Точка. Створіть клас Точка Point, що до-
зволяє працювати з координатами (x, y). Додайте необхідні ме-
тоди класу.
4. Особистий зоопарк.
4.1. Див. перший пункт завдання "Створення тварини".

128
4.2. Нехай Animal буде батьківським для класу Cat. Метод
makeNoise () класу Cat виводить "(Name) говорить Мяу". Конс-
труктор класу Cat виводить "Народився кіт", а також викликає
батьківський конструктор.
4.3. Нехай Animal буде батьківським для класу Dog. Метод
makeNoise () для Dog виводить "(Name) говорить Гав". Констру-
ктор Dog виводить "Народився собака", а також викликає бать-
ківський конструктор.
4.4. Основна програма. Код, що створює кота, двох собак і од-
ну просто тварину. Дайте ім'я кожній тварині (через виклик мето-
дів). Код, що викликає eat () і makeNoise () для кожної тварини.

1.15. Проекти ігор


Після отримання необхідного мінімуму знань зі складу мови
програмування Python25 читачеві пропонується для закріплення
навичок ознайомитися із двома проектами. З досвіду авторів,
щоб розібратися в будь-якій мові програмування й відточити
свою майстерність, необхідно зайнятися деяким цікавим і не
дуже складним проектом.
У пропонованих проектах розглядається програмування двох
простих ігор: "Шибениця" і "Пінг-Понг". У першому проекті
детально обговорюється алгоритм гри за допомогою блок-схем,
а для візуалізації процесу гри використовується псевдографіка.
У другому проекті використовується вже графічний інтерфейс,
який створюється за допомогою відповідної бібліотеки. У про-
понованих проектах надається код з докладними поясненнями й
коментарями. Крім того, читачеві пропонується вдосконалити
код розглянутих ігор.

25
Мова програмування, так само як і будь-яка природна мова, має:
• алфавіт – символи, які можна використовувати в програмах;
• лексику – ключові слова, ідентифікатори, оператори;
• пунктуацію – знаки (; ,. :) і дужки () {} [];
• синтаксис – вирази, конструкції;
• семантику – значення елементів мови для програміста і для ком-
п'ютера.
129
1.15.1. Проект № 1. Гра "Шибениця"

Правила гри "Шибениця". Шибениця – це гра для двох, у


якій один гравець загадує слово і рисує на сторінці окремі по-
рожні клітини для кожної літери, а другий намагається вгадати
літери, які можуть бути в слові, а потім і все слово.
Якщо другий гравець правильно вгадує літеру, то перший впи-
сує її у відповідну клітину; якщо ж другий гравець помиляється,
то перший рисує одну з частин тіла повішеного чоловічка. Щоб
перемогти, другий гравець повинен угадати всі літери в слові до
того, як повішений чоловічок буде повністю нарисований.
ASCII-графіка. Графіка для гри "Шибениця" – це символи
клавіатури, надруковані на екрані. Такий вид графіки називаєть-
ся ASCII-графікою. Вона була попередником популярних зараз
"Емодзі". ASCII-графіка для гри "Шибениця" має такий вигляд:
+––+ +––+ +––+ +––+ +––+ +––+ +––+
| o | o | o | o | o | o |
| | | | /| | /|\ | /|\ | /|\ |
| | | | | / | /\ |
–- –- –- –- –- –- –-

Проектування гри за допомогою блок-схеми. Гра містить


складний код, тому розглянемо етапи її програмування. Для
початку треба створити блок-схему, щоб наочно уявити те, що
буде робити програма.
Блок-схема – це діаграма, що відображає серію кроків у вигляді
полів (блоків), зв'язаних один з одним стрілками. Кожне поле – це
крок, а стрілки показують можливі наступні кроки. Помістіть палець
на поле "Старт" блок-схеми і простежте за ходом програми, пересу-
ваючись за стрілками на інші поля, поки не дійдете до поля "Кінець".
Ви можете рухатися від поля до поля тільки за стрілками. Не
можна рухатися назад до тих пір, поки стрілка вас туди не пере-
несе, як у випадку з полем "Гравець уже називав цю літеру". На
схемі 1.1 показана повна блок-схема гри "Шибениця".
Ви не зобов'язані складати блок-схему, а можете відразу пи-
сати код. Однак часто буває так, що як тільки ви починаєте пи-
сати програму, вам на думку спадають речі, які ви хочете додати
або змінити. Усе може закінчитися тим, що вам доведеться ви-
130
далити значний фрагмент вашого коду, що буде означати марну
витрату часу. Найкращий спосіб цього уникнути – спланувати,
як має працювати програма, до того, як почати писати її код.
Створення блок-схеми. Вигляд вашої блок-схеми необов'язково
має бути таким, як на схемі 1.1. Головне, щоб ви розуміли свою блок-
схему, це вам допоможе, коли почнете писати код. Ви можете спрое-
ктувати свою блок-схему, почавши з полів "Старт" і "Кінець", як
показано на схемі 1.2. Тепер подумайте про те, що відбувається, коли
ви граєте у гру "Шибениця". Для початку комп'ютер "задумує" слово.
Потім гравець угадує літери. Додайте поля для цих дій, як показано
на схемі 1.3. Нові поля в кожній блок-схемі обведені пунктиром.

Старт

Задумуємо слово

Показуємо гравцю
шибеницю і пропуски

Просимо гравця
вгадати літеру

Так Ні
Наявність літери
у слові
Так
Відповідний пропуск Чоловічка
заповнюється літерою нарисовано
Ні Ні
Слово Рисується наступний
відгадано елемент чоловічка
Так
Ще гра
Ні Так

Кінець

Схема 1.1. Повна блок-схема гри "Шибениця"

131
Кінець

Старт Старт

Задумуємо слово

Показуємо гравцю
Кінець шибеницю і пропуски

Просимо гравця
вгадати літеру

Кінець

Схема 1.2. Починаємо проек- Схема 1.3. Додаємо додаткові


тувати блок-схему з полів три поля з описом кроків у грі
"Старт" і "Кінець" "Шибениця"

Однак гра не закінчується після того, як гравець угадує літеру.


Програмі необхідно перевірити, чи містить задумане слово цю літеру.
Розгалуження в блок-схемах. Існують два сценарії: літера є
у слові або її в ньому немає. Ви додаєте два нові блоки у схему –
по одному для кожного зі сценаріїв розвитку подій. Як створю-
ється розгалуження у блок-схемі, показано на схемі 1.4.

Старт
Просимо гравця
Задумуємо слово вгадати літеру

Показуємо гравцю Так Ні


шибеницю і пропуски Наявність
літери у слові

Кінець

Схема 1.4. Розгалуження на дві окремі гілки

132
Якщо літера є у згаданому слові, то слід перевірити: можли-
во, гравець угадав усі літери та виграв. Якщо літери у слові не-
має, то треба перевірити: можливо, повішений чоловічок нари-
сований повністю і гравець програв. Додайте поля для цих дій.
Блок-схема тепер виглядає так, як показано на схемі 1.5.
Наступна спроба. Тепер блок-схема здебільшого завершена, але
нам усе ще дечого бракує. По-перше, гравець не вгадує літеру тіль-
ки один раз; він продовжує вгадувати літери доти, поки не виграє
або програє. Дорисуйте дві нові стрілки, як показано на схемі 1.6.
Проміжні результати гри. Гравцю необхідно знати проміж-
ні результати гри. Програма має відобразити рисунок повішено-
го чоловічка і секретне слово з пробілами замість літер, які гра-
вець ще не вгадав. Цей інтерфейс дозволить гравцю зрозуміти,
наскільки він близький до перемоги або програшу.

Старт

Задумуємо слово

Показуємо гравцю
шибеницю і пропуски

Просимо гравця
вгадати літеру

Так Ні
Наявність літери
у слові
Відповідний пропуск Чоловічка
заповнюється літерою намальовано

Ні Ні
Рисується наступний
Слово відгадано
елемент чоловічка
Кінець

Схема 1.5. Після розгалуження кроки продовжуються в різних напрямах

133
Інформація оновлюється кожного разу, коли гравець на-
магається вгадати літеру. Додайте блок "Показуємо гравцеві
шибеницю і пробіли" у схему між блоками "Задумується
секретне слово" і "Просимо гравця вгадати літеру ", як пока-
зано на схемі 1.6.
Закінчуємо або починаємо гру спочатку. Коли гравець ви-
грав або програв, запитуємо його, чи хоче він зіграти
заново, щоб відгадати інше слово. Якщо гравець не хоче грати,
то програма завершує роботу; в іншому випадку програма про-
довжує виконання і загадує нове слово (див. схему 1.7).

Старт

Задумуємо слово

Показуємо гравцю
шибеницю і пропуски

Просимо гравця
вгадати літеру
Так Ні
Наявність літери у слові

Відповідний пропуск
Чоловічка нарисовано
заповнюється літерою
Ні
Ні Рисується наступний
Слово відгадано елемент чоловічка
Кінець

Схема 1.6. Пунктирні лінії показують,


що гравець може вгадувати знову

134
Старт

Задумуємо слово

Показуємо гравцю
шибеницю і пропуски

Просимо гравця
вгадати літеру

Так Ні
Наявність літери у слові

Відповідний пропуск Так Чоловічка


заповнюється літерою нарисовано
Ні

Слово відгадано Рисується наступний


елемент чоловічка
Ні Так
Так
Ще гра

Ні
Кінець

Схема 1.7. Гілка блок-схеми із запитом, чи хоче гравець зіграти знову

Ця блок-схема повністю описує порядок ігрового процесу


додатка "Шибениця". Блок-схема допоможе вам не пропустити
нічого з того, що слід урахувати при розробці гри.
Вам може здатися нудним рисувати блок-схеми перед напи-
санням програми. Зрозуміло, що люди бажають грати в ігри, а
не розглядати блок-схеми. Однак набагато легше вносити зміни
й виявляти проблеми, уявляючи, як буде працювати програма,
перш ніж писати її код.
Якщо ви відразу приступите до написання коду, то можете
виявити проблеми, які вимагають зміни вже написаного коду,
що буде марною тратою часу і зусиль. До того ж кожного разу,
змінюючи код, ви ризикуєте припуститися нових помилок, вно-

135
сячи зміни. Набагато ефективніше знати, що ви хочете побуду-
вати в підсумку, перш ніж приступати до будівництва. Тепер,
коли у нас є блок-схема, давайте створимо гру "Шибениця".
Оригінальний код гри "Шибениця". Велика частина
коду цієї гри припадає на ASCII-код для прорисовування пові-
шеного чоловічка.
Введіть наведений нижче вихідний код і збережіть файл під
ім'ям hangman.py. Потім запустіть програму.
1. import random
2. HANGMAN_PICS = ['''
3. +–-+
4. |
5. |
6. |
7. ===''', '''
8. +–-+
9. |
10. |
11. |
12. ===''', '''
13. +–-+
14. 0 |
15. | |
16. |
17. ===''', '''
18. +–-+
19. 0 |
20. /| |
21. |
22. ===''', '''
23. +–-+
24. 0 |
25. /|\ |
26. |
27. ===''', '''
28. +–-+
29. 0 |

136
30. /|\ |
31. / |
32. ===''', '''
33. +–-+
34. 0 |
35. /|\ |
36. / \ |37. ===''']
38. words = 'лелека акула бабуїн баран борсук бобер бик вер-
блюд вовк горобець ворон видра голуб гусак жаба зебра змія
індик кит кобра коза козел койот корова кішка кролик щур кур-
ка лама ласка лебідь лев лисиця лосось лось жаба ведмідь мо-
люск моль мул мурашка миша норка носоріг мавпа вівця окунь
олень орел віслюк панда павук Python папуга пума сьомга скунс
собака сова тигр тритон тюлень качка форель тхір черепаха яст-
руб ящірка '.split()
39.
40. def getRandomWord(wordList):
41. # Ця функція повертає випадковий рядок з переданого
списку
42. wordIndex = random.randint(0, len(wordList) – 1)
43. return wordList[wordIndex]
44.
45. def displayBoard(missedLetters, correctLetters, secretWord):
46. print(HANGMAN_PICS[len(missedLetters)])
47. print()
48.
49. print('Помилкові літери:', end=' ')
50. for letter in missedLetters:
51. print(letter, end=' ')
52. print()
53.
54. blanks = '_' * len(secretWord)
55.
56. for i in range(len(secretWord)):#замінює пробіли відга-
даними
літерами
57. if secretWord[i] in correctLetters:

137
58. blanks = blanks[:i] + secretWord[i] + blanks[i+1:]
59.
60. for letter in blanks: # Показує секретне слово з пробіла-
ми між
літерами
61. print(letter, end=' ')
62. print()
63.
64. def getGuess(alreadyGuessed):
65. # Повертає літеру, введену гравцем. Ця функція переві-
ряє, що гравець увів тільки одну літеру і нічого більше
66. while True:
67. print('Введіть літеру.')
68. guess = input()
69. guess = guess.lower()
70. if len(guess) != 1:
71. print('Будь ласка, введіть одну літеру.')
72. elif guess in alreadyGuessed:
73. print('Ви вже називали цю літеру. Назвіть іншу.')
74. elif guess not in 'абвгґдеєжзиіїйклмнопрстуфхц-
чшщьюя':
75. print('Будь ласка, введіть ЛІТЕРУ.')
76. else:
77. return guess
78.
79. def playAgain():
80. #Ця функція повертає значення True, якщо гравець хо-
че зіграти знову; в іншому випадку повертає False.
81. print('Бажаєте зіграти ще? (так чи ні)')
82. return input().lower().startswith('т')
83.
84.
85. print('Ш И Б Е Н И Ц Я')
86. missedLetters = ''
87. correctLetters = ''
88. secretWord = getRandomWord(words)
89. gameIsDone = False

138
90.
91. while True:
92. displayBoard(missedLetters, correctLetters, secretWord)
93.
94. # Дозволяє гравцеві ввести літеру.
95. guess = getGuess(missedLetters + correctLetters)
96.
97. if guess in secretWord:
98. correctLetters = correctLetters + guess
99.
100. # Перевіряє, чи виграв гравець.
101. foundAllLetters = True
102. for i in range(len(secretWord)):
103. if secretWord[i] not in correctLetters:
104. foundAllLetters = False
105. break
106. if foundAllLetters:
107. print('ТАК! Секретне слово – "' + secretWord + '"!
Ви вгадали!')
108. gameIsDone = True
109. else:
110. missedLetters = missedLetters + guess
111.
112. # Перевіряє, чи перевищив гравець ліміт спроб і
програв.
113. if len(missedLetters) == len(HANGMAN_PICS) – 1:
114. displayBoard(missedLetters, correctLetters, secretWord)
115. print(' Ви вичерпали всі спроби!\n Не угадано лі-
тер: ' + str(len(missedLetters)) + ' і угадано літер: ' +
str(len(correctLetters)) + '. Було загадано слово "' + secretWord + '".')
116. gameIsDone = True
117.
118. # Запитує, чи хоче гравець зіграти знову (тільки якщо
гра завершена).
119. if gameIsDone:
120. if playAgain():
121. missedLetters = ''

139
122. correctLetters = ''
123. gameIsDone = False
124. secretWord = getRandomWord(words)
125. else:
126. break
Імпортування модуля random. Програма "Шибениця" ви-
падковим чином вибирає слово із заздалегідь визначеного спис-
ку і пропонує вгадати його гравцеві. Можливість випадкового
вибору забезпечує модуль random, який імпортується в першому
рядку коду.
1. import random
Змінна HANGMAN_PICS у другому рядку трохи відрізняєть-
ся від змінних, з якими ви вже знайомі. Для розуміння наступ-
ного коду ви повинні познайомитися з кількома додатковими
концепціями.
Константи. Рядки з 2 по 37 – це одна довга інструкція при-
своювання для змінної HANGMAN_PICS.
2. HANGMAN_PICS = ['''
3. +–-+
4. |
5. |
6. |
7. ===''', '''
–пропущене–
37. ===''']
Існують неформальні угоди запису імен, наприклад: якщо
ім'я змінної складається із великих літер, то за умовчанням це є
константа.
Константи – це змінні, значення яких не змінюються в проце-
сі роботи програми з моменту їх оголошення. Теоретично ви
можете змінити значення змінної HANGMAN_PICS так само
просто, як і будь-якої іншої змінної, тому ім'я великими літера-
ми нагадує вам, щоб ви цього не робили.
Як і у випадку з усіма іншими угодами з оформлення коду,
ви зовсім не зобов'язані їх дотримуватися. Однак, дотримуючись
їх, ви зробите код зрозумілішим для інших програмістів.
140
Списки. Значення змінної HANGMAN_PICS складається із
кількох рядків. Це називається списком. У списках як елементи
можуть міститися різні значення.
Список значень починається і закінчується квадратними ду-
жками – []. Рядкові значення (слова) поміщаються в одинарні
лапки – '' і розділяються комами.
Ці слова називаються елементами списку. Кожен елемент
HANGMAN_PICS – це багаторядкове значення.
Списки дозволяють зберігати кілька значень, не призначаючи
окрему змінну для кожного з них.
Доступ до елементів за їх індексами. Можна отримати до-
ступ до будь-якого елемента списку, указавши його номер у
квадратних дужках у кінці імені змінної. Номер у квадратних
дужках називається індексом елемента. У Python першому еле-
менту присвоюється індекс 0. Другий елемент має індекс 1, тре-
тій – 2 і т. д. Оскільки індекси в списках починаються з 0, а не з
1, то їх називають списками нульового індексу.
Індекс за межами діапазону і помилка IndexError. Якщо
вказати індекс, значення якого перевищує кількість записів у
списку, то виконання програми буде перервано і з'явиться по-
відомлення про помилку IndexError.
Конкатенація списків. Можна з'єднати кілька списків в
один, як і звичайні рядки, використовуючи оператор +.
Оператор in. Оператор in дозволяє визначити наявність у
списку елемента з певним значенням. Вирази з оператором in
повертають значення True, якщо шукане значення міститься у
списку, або False, якщо його там немає.
Оператор in також застосовується до рядків. У цьому випад-
ку він перевіряє, чи є один рядок частиною іншого.
Виклик методів. Метод – це функція, прив'язана до об'єкта (спи-
ску або рядка). Для виклику методу необхідно прив'язати його до
конкретного об'єкта. У мові Python вбудовано багато корисних ме-
тодів, деякі з них ми використовуємо у грі "Шибениця". Однак спо-
чатку розглянемо кілька методів для роботи зі списками та рядками.
Методи списків reverse () та append (). Списки підтримують
два вбудовані методи, які використовуються найчастіше, – reverse
() та append (). Метод reverse () розгортає (звертає) список.
Однак найчастіше у списках використовують метод append ().
Він додає нові значення в кінець списку.
141
Зазначені методи змінюють списки, які їх викликають, і не
створюють нові списки. Це називається зміною списків за місцем.
Рядковий метод split (). Рядки мають вбудований метод split
(), який повертає список, сформований з рядкових змінних, на
які розбивається рядок. У 38-му рядку коду гри "Шибениця" теж
використовується метод split (), як показано нижче. Код здається
довгим, але насправді це просто довгий рядок слів, розділених
пробілами, з викликом методу split () у кінці.
Метод split () створює список, у якому кожне слово з рядка
стає його окремим елементом.
38. words = 'лелека акула бабуїн баран борсук бобер бик верблюд
вовк горобець ворон видра голуб гусак жаба зебра змія індик кит
кобра коза козел койот корова кішка кролик щур курка лама ласка
лебідь лев лисиця лосось лось жаба ведмідь молюск моль мул му-
рашка миша норка носоріг мавпа вівця окунь олень орел віслюк
панда павук Python папуга пума сьомга скунс собака сова тигр три-
тон тюлень качка форель тхір черепаха яструб ящірка'.split ()
Метод split () полегшує програмування. Дійсно, щоб створи-
ти список, треба записати слова в одинарних лапках через кому
й помістити їх у квадратні дужки.
Ви можете додати власні слова до коду рядка 38 або видали-
ти які-небудь із них, якщо не хочете, щоб вони були у грі. Голо-
вне, переконайтеся, що слова розділені пробілами.
Отримання секретного слова зі списку. У рядку 40 визна-
чається функція getRandomWord (). Значення елементів списку
передаються аргументу wordlist як параметри. Ця функція пове-
ртає одиничне секретне слово зі списку.
40. def getRandomWord (wordList):
41. # Ця функція повертає випадковий рядок з переданого списку.
42. wordIndex = random.randint (0, len (wordList) – 1)
43. return wordList [wordIndex]
У рядку 42 як значення змінної wordIndex зберігається елемент
із випадковим індексом. Випадковий вибір елемента виконується
функцією randint () із двома аргументами. Перший аргумент – це
0 (для першого можливого індексу), а другий – значення виразу
len (wordList) – 1, що визначає останній можливий індекс.

142
Нагадаємо, що індексація елементів списку починається з 0, а
не з 1. Якщо є список із трьох елементів, то індекс першого еле-
мента – 0, другого – 1, а третього – 2. Довжина такого списку
дорівнює 3, але індексу 3 у списку немає. Тому в рядку 42 з до-
вжини списку віднімається одиниця. Код у рядку 42 працює
незалежно від розмірів списку wordList. Тепер ви можете спо-
кійно додавати й видаляти рядки зі списку wordList.
Змінна wordIndex зберігає випадковий індекс зі списку, пере-
даного за допомогою параметра wordList. Код у рядку 43 повер-
тає зі списку wordList значення елемента з відповідним індек-
сом, який зберігається як ціле число у wordIndex.
Припустимо, що ['яблуко', 'апельсин', 'виноград'] передається як
аргумент у функцію getRandomWord (), тоді randint (0, 2) повертає 2.
Це означає, що рядок 43, у якому повертається значення wordList [2],
поверне значення 'виноград'. Таким чином функція getRandomWord
() повертає випадковий рядок зі списку wordList, тобто вхідними
даними для функції getRandomWord () є список рядків, а вихідними
– випадково обраний рядок. У грі "Шибениця" таким чином вибира-
ється секретне слово, яке буде вгадувати гравець.
Відображення ігрового поля для гравця. Далі необхідна
функція прорисовування ігрового поля гри "Шибениця". На
ньому має відображатися кількість уведених гравцем літер, уга-
даних як правильно, так і помилково.
45. def displayBoard (missedLetters, correctLetters, secretWord):
46. print (HANGMAN_PICS [len (missedLetters)])
47. print ()
У наступному коді визначається функція з ім'ям displayBoard
(), яка має три параметри:
• missedLetters – рядок літер, які гравець назвав, але їх немає
в задуманому слові;
• correctLetters – рядок літер, які гравець угадав у задуманому
слові;
• secretWord – рядок із задуманим словом, яке гравець нама-
гається вгадати.
Спочатку функція print () викликає відображення ігрового
поля. Глобальна змінна HANGMAN_PICS містить список ряд-
кових змінних для прорисовування всіх можливих етапів гри
(нагадаємо, що глобальні змінні доступні з функції). Код
143
HANGMAN_PICS [0] відображає порожню шибеницю, код
HANGMAN_PICS [1] показує голову (коли гравець назвав не-
правильно одну літеру), код HANGMAN_PICS [2] – голову і
тіло (коли гравець неправильно назвав дві літери) і так далі до
HANGMAN_PICS [6], що показує шибеника цілком.
Кількість літер, що зберігається у змінній missedLetters, відо-
бражає кількість неправильних припущень, зроблених гравцем.
Для визначення цієї кількості викликається функція len
(missedLetters). Отже, якщо значення змінної missedLetters одне,
наприклад 'лелека', то код len ('лелека') поверне 4. Код
HANGMAN_PICS [4] відобразить повішеного, що відповідає
чотирьом помилкам. Це те, на що перетвориться код
HANGMAN_PICS [len (missedLetters)] у рядку 46.
Код у рядку 49 виводить повідомлення 'Помилкові літери:' із
символом пробілу в кінці замість символу нового рядка.
49. print ('Помилкові літери:', end = '')
50. for letter in missedLetters:
51. print (letter, end = '')
52. print ()
У рядку 50 починається цикл for, у якому відбувається пере-
бирання всіх символів із рядкового значення змінної
missedLetters і виведення їх на екран.
Нагадаємо, що вираз end = '' заміщає символ нового рядка,
який поміщається в кінці кожного рядка, одиничним пробілом.
Наприклад, якщо значенням missedLetters є 'аизх', то такий цикл
for виведе на екран а и з х.
Інша частина коду функції displayBoard () (рядки 54–62) ви-
водить на екран літери й формує рядок – секретне слово, у яко-
му ще не вгадані літери, заміщені пробілами. Це досягається за
допомогою функції range () і зрізу списку.
Функції list () та range (). Функція range (), що викликається
з одним аргументом, повертає послідовність чисел від 0 до ве-
личини аргументу; сам аргумент у послідовність не включаєть-
ся. Таку послідовність можна використовувати в циклі for або
перетворити на список за допомогою функції list ().
Функція list () дуже схожа на функції str () або int (). Вона при-
ймає послідовність величин і повертає їх у вигляді списку. За допо-
могою функцій list () і range () легко генерувати величезні списки.
144
Якщо функція range () викликається із двома цілочисловими
аргументами, то вона повертає послідовність чисел, починаючи
з першого аргументу до (не включаючи) другого.
Зрізи списків і рядків. Зріз списку створює новий список з
підмножиною елементів батьківського списку. Для створення
зрізу списку використовуються два індекси (початковий і кінце-
вий), які поміщаються в кінець імені у квадратних дужках і роз-
діляються двокрапкою.
Виведення секретного слова з пробілами. За бажання мож-
на вивести секретне слово з пробілами замість невгаданих літер.
Можна також використовувати символ нижнього підкреслення
(_). Спочатку треба створити рядок із символів нижнього під-
креслення для всіх літер секретного слова, потім замінити про-
біли цими символами.
Наприклад, для секретного слова 'ворон' порожній рядок-
підкреслення має такий вигляд: '_ _ _ _ _' (п'ять підкреслень).
Якщо значення змінної correctLeters одне – 'ворон', то секретне
слово треба надрукувати у вигляді '_ о _ о н'. Код у рядках 54–58
робить саме це.
54. blanks = '_' * len (secretWord)
55.
56. for i in range (len (secretWord)): # замінює пробіли відга-
даними літерами
57. if secretWord [i] in correctLetters:
58. blanks = blanks [: i] + secretWord [i] + blanks [i + 1:]
У рядку 54 створюється рядкова змінна blanks із підкресленнями
за кількістю літер секретного слова. Нагадаємо, що оператор * засто-
совується для змінних рядкового і цілого типів, тому вираз '_' * 5
буде перетворено на рядок '_ _ _ _ _'. Ця операція гарантує, що змінна
blanks містить стільки підкреслень, скільки літер у секретному слові.
Код у рядку 56 у циклі for здійснює послідовне перебирання
всіх літер секретного слова і заміщає підкреслення літерами, що
містяться у змінній correctLetters.
Розглянемо цикл із попереднього прикладу з іншими дани-
ми. Нехай секретне слово 'ворон', а значення змінної
correcLetters одне – 'он'. Треба вивести на екран рядок '_ о _ о
н'. Подумаємо, як це зробити.
145
Код у рядку 56 після виклику len (secretWord) поверне зна-
чення 5. Виклик range (len (secretWord)) набуде вигляду range
(5), тоді цикл for зробить ітерації від 0 до 4 включно.
Оскільки змінна i буде послідовно набувати значень [0, 1, 2,
3, 4], то код циклу for матиме приблизно такий вигляд:
if secretWord[0] in correctLetters:
blanks = blanks[:0] + secretWord[0] + blanks[1:]
if secretWord[1] in correctLetters:
blanks = blanks[:1] + secretWord[1] + blanks[2:]
–пробіл–
Ми показали тільки дві перші ітерації циклу for, але насправ-
ді змінна ітерації i по черзі набуватиме всіх значень послідовно-
сті, починаючи з 0.
У першій ітерації i дорівнюватиме 0, тому оператор if переві-
рить, чи міститься літера секретного слова з індексом 0 у рядку
correctLetters. Ця перевірка здійснюватиметься в циклі для кож-
ної літери секретного слова, по одній ітерації на кожну літеру.
Якщо ви сумніваєтеся стосовно будь-якого значення, напри-
клад secretWord [0] або blank [3:], то зверніться до рис. 1.8, де
наведені значення secretWord, змінної blanks та індекси для ко-
жної літери в рядку.

_ _ _ _ _
blanks
0 1 2 3 4

В О Р О Н
secretWord
0 1 2 3 4

Рис. 1.8

Якщо зріз списку і список індексів замінити значеннями їхніх


елементів, то код циклу матиме приблизно такий вигляд:
if 'у' in 'он': # False
blanks = '' + 'у' + '____' # Код у рядку пропускається.
–пробіл–

146
if 'н' in 'он': # True
blanks = '_о_о_' + 'н' + '' # Код у рядку виконується.
# Змінній blanks присвоєно значення _о_он '.
Код з наведеного вище прикладу робить те саме, коли зна-
чення secretWord одне – 'ворон', а значення correctLetters одне –
'он'. Наступний фрагмент коду виводить на екран нове значення
blanks із пробілами між літерами.
60. for letter in blanks: # Показує секретне слово з пробілами
між літерами
61. print (letter, end = '')
62. print ()
Зазначимо, що цикл for у рядку 60 не викликає функцію range ().
Замість ранжирування операцій послідовністю, що повертає range,
ітерації проводяться за значенням рядкової змінної blanks. При ко-
жній ітерації береться одна нова літера з рядка 'ворон' змінної
blanks. У результаті після додавання пробілів буде виведено '_о_он'.
Отримання припущень гравця. При виконанні функції
getGuess () гравець може ввести передбачувану літеру. Функція
повертає літеру, запропоновану гравцем, у вигляді рядка.
Далі функція getGuess () перевіряє допустимість уведеного
символу, перш ніж передати його у функцію.
64. def getGuess (alreadyGuessed):
65. # Повертає літеру, уведену гравцем. Функція перевіряє,
чи гравець увів тільки одну літеру і нічого більше.
Рядок літер, запропонованих гравцем, передається як аргумент
у параметр alreadyGuessed функції. Потім функція getGuess () про-
понує гравцю ввести одну літеру, яку поверне як своє значення.
Оскільки Python чутливий до регістру, то слід переконатися, що
введена мала літера, щоб її можна було коректно зіставити з літе-
рами секретного слова. Для цього знадобиться метод lower ().
Рядкові методи lower () та upper (). Метод lower () повертає
рядок, у якому всі літери рядкові. Існує також зворотний рядко-
вий метод upper (), який повертає рядок з великими літерами.
Завершення циклу while. У рядку 66 цикл while буде вима-
гати від гравця ввести літеру до тих пір, поки не буде введена
літера, яка раніше не пропонувалася.
147
Умовою циклу while є логічна змінна в значенні True. Це
означає, що єдиним способом завершити цикл є виконання ін-
струкції break або інструкції return, яка забезпечує вихід не тіль-
ки з циклу, але й із функції.
Внутрішній код циклу пропонує гравцеві ввести літеру, яка
буде збережена в змінній guess. Якщо гравець уведе велику лі-
теру, то вона буде перетворена на малу за допомогою коду в
рядку 69.
Інструкції elif. Наступна частина коду гри "Шибениця" міс-
тить інструкції elif. Інструкція elif означає: якщо це істинно, то
зробити так, а якщо виконується інша умова, то зробити так;
якщо ж не виконується жодна умова, то зробити ось так.
Якщо умова однієї інструкції elif істинна, то виконується код
саме цієї інструкції, а потім відбувається повернення до першо-
го рядка циклу. Таким чином, за один прохід виконується код
одного й тільки одного програмного блоку конструкції if-elif-
else. Інструкція else припиняє роботу блоку if, якщо більше не
треба перевіряти виконання його умов.
Перевірка допустимості припущення гравця. Змінна guess
містить літери, запропоновані гравцем. Програма має впевнити-
ся, що вводяться допустимі символи. Може бути тільки одна
літера, при цьому не мають застосовуватися літери, що вводи-
лись раніше. Якщо ця умова не виконується, то цикл поверта-
ється до початку і знову запитує літеру.
70. if len (guess)! = 1:
71. print ('Будь ласка, введіть одну літеру.')
72. elif guess in alreadyGuessed:
73. print ('Ви вже називали цю літеру. Назвіть іншу.')
74. elif guess not in 'абвгдеежзійклмнопрстуфхцчшщ'иьеюя':
75. print ('Будь ласка, введіть ЛІТЕРУ.')
76. else:
77. return guess
У рядку 70 перевіряється, чи введено не більше одного сим-
волу; код у рядку 72 перевіряє, чи запропонована літера не міс-
титься у змінній alreadyGuessed, код у рядку 74 – чи введено
символ стандартного українського алфавіту.

148
Якщо хоча б одна умова не виконується, то гравцеві пропо-
нується ввести іншу літеру. Якщо виконано всі умови, то про-
грама переходить до виконання коду блоку і повертає значення
запропонованої літери в рядку 77.
Нагадаємо, що в конструкції if-elif-else виконується тільки
один програмний блок.
Пропозиція гравцеві зіграти знову. Функція playAgain ()
містить лише виклик функції print () та інструкцію return.
79. def playAgain ():
80. # Ця функція повертає значення True, якщо гравець хоче
зіграти знову; в іншому випадку повертає False.
81. print ('Бажаєте зіграти ще? (Так чи ні)')
82. return input (). Lower (). Startswith ('д')
Інструкція return завершує виконання програми, але її дію
можна скасувати. Розглянемо докладніше, які перетворення
здійснює Python, якщо гравець відповів ТАК.
input (). lower (). startswith ('y')
'YES'.lower (). Startswith (' y ')
'Yes'.startswith (' y ')
True
т
т
ат
ТАК
Функція playAgain () дозволяє гравцеві відповісти Так або Ні
на пропозицію ще одного раунду. Гравець повинен увести Так,
так, Т або що-небудь, що починається з літери т, і це значення
буде означати Так. Якщо гравець увів ТАК, то функція input ()
повертає значення 'ТАК'. Вираз 'ТАК'.lower () повертає рядок у
нижньому регістрі (маленькими літерами), тобто значення 'ТАК'
перетворюється на "так". Крім цього, виконується ще й виклик
методу startswith ('т').
Ця функція повертає True, якщо викликаний рядок почина-
ється зі вказаного параметра, або False, якщо це не так. Напри-
клад, вираз "так".
startwith ('т') повертає значення True.

149
Це єдиний сенс виразу. Він дозволяє гравцеві ввести відпо-
відь, переводить його у рядкову форму, перевіряє, чи відповідь
починається з літери "т", і потім повертає True або False.
Огляд функцій гри. Коротко розглянемо функції, що вико-
ристовуються у грі "Шибениця".
• getRandomWord (wordList) випадковим чином вибирає один ря-
док зі списку. Так вибирається слово, яке буде вгадувати гравець.
• displayBoard (missedLetters, correctLetters, secretWord) відобра-
жає поточний стан гри, показує, скільки літер гравець уже запро-
понував і які з них виявилися помилковими. Для коректної роботи
функції необхідні три параметри: рядкові змінні correctLetters і
missedLetter для зберігання літер, які були запропоновані гравцем, і
тих, яких не виявилося в секретному слові, а також рядкова змінна
secretWord, що містить секретне слово, яке намагається вгадати
гравець. Функція не повертає будь-яких значень.
• getGuess (alreadyGuessed) перевіряє, чи не міститься введена
гравцем літера в рядковій змінній alreadyGuessed. Функція пове-
ртає літеру, уведену гравцем, якщо вона допустима.
• playAgain () запитує гравця, чи не хоче він зіграти ще раз.
Функція повертає True, якщо гравець погоджується, або False,
якщо відмовляється.
Після опису функцій з рядка 85 починається основна частина
(тіло) програми. Усе, що вище цього рядка, – це опис функцій і
код інструкції присвоювання для змінної HANGMAN_PICS.
Ігровий цикл. Основна частина коду програми "Шибениця"
відображає на екрані назву гри, містить кілька змінних і запус-
кає цикл while. Розглянемо послідовне виконання решти про-
грамного коду.
85. print ('Ш И Б Е Н И Ц Я')
86. missedLetters = ''
87. correctLetters = ''
88. secretWord = getRandomWord (words)
89. gameIsDone = False
Код у рядку 85 викликає функцію print (), яка виводить на ек-
ран заголовок гри в момент її запуску. Потім рядковій змінній
missedLetters присвоюється порожнє значення; змінній
correctLetters теж присвоюється порожнє значення, оскільки
гравець не запропонував ще ніяких літер.
150
У рядку 88 викликається функція getRandomWord (words),
яка вибирає випадковим чином секретне слово зі списку.
Код у рядку 89 присвоює змінній gameIsDone значення False;
він присвоїть їй значення True, коли надійде сигнал про завер-
шення гри, і запропонує гравцеві зіграти заново.
Виклик функції displayBoard (). Частина програми, що за-
лишилася, складається із циклу while. Умова циклу завжди іс-
тинна, а це значить, що він буде виконуватися нескінченно дов-
го, доти, поки не буде ініційовано виконання інструкції break (це
відбувається в рядку 126).
91. while True:
92. displayBoard(missedLetters, correctLetters, secretWord)
Код у рядку 92 викликає функцію displayBoard (), передаючи їй
значення трьох змінних, установлених у рядках 86–88. Залежно від
того, скільки літер гравець угадав правильно і скільки разів поми-
лився, функція виводить на екран зображення повішеного.
Введення гравцем літери, яка вгадується. Далі виклика-
ється функція getGuess (), щоб гравець міг увести літеру, що
вгадується.
94. # Дозволяє гравцеві ввести літеру.
95. guess = getGuess (missedLetters + correctLetters)
У функцію передається параметр alreadyGuessed для визна-
чення, вводив або ні гравець таку літеру раніше. Код у рядку 95
конкатенує рядкові змінні missed Letters і correct Letters, а потім
передає результат як аргумент параметру alreadyGuessed.
Перевірка наявності літери в секретному слові. Якщо за-
пропонована літера є в секретному слові, то вона додається в
кінець рядкової змінної correctLetters.
97. if guess in secretWord:
98. correctLetters = correctLetters + guess
Код в останньому рядку додає нове значення у змінну
correctLetters.
Перевірка факту перемоги гравця. Як програмі дізнатися,
що гравець угадав усі літери, які містяться в секретному слові?
Змінна correctLetters містить усі літери, що вгадані гравцем пра-
вильно, а змінна secretWord – секретне слово. Однак просту пе-
151
ревірку missedLetters == correctLetters зробити неможливо. Як-
що, наприклад, секретне слово 'ворон', а correctLetters – 'внро', то
значення виразу correctLetters == secretWord буде помилковим,
хоча гравець угадав усі літери.
Єдино можливий шлях – виконати порівняння кожної літери
із correctLetters із літерами в змінній secretWord. Гравець пере-
магає тоді й тільки тоді, коли кожна літера зі змінної secretWord
міститиметься в змінній correctLetters.
100. # Перевіряє, чи виграв гравець.
101. foundAllLetters = True
102. for i in range(len(secretWord)):
103. if secretWord[i] not in correctLetters:
104. foundAllLetters = False
105. break
Зрозуміло, що якщо в змінній secretWord знайдена літера,
якої немає в змінній correctLetters, то гравець не переміг. Нова
змінна foundAllLetters зі значенням True встановлюється в рядку
101 до початку циклу. Цикл починається в припущенні, що всі
літери секретного слова вгадані. Однак у рядку 104 у процесі
виконання циклу значення змінної foundAllLetters змінюється на
False, як тільки виявляється перша літера зі змінної secretWord,
що не міститься в змінній correctLettrs.
Якщо всі літери секретного слова виявлені, то гравцеві по-
відомляється про його перемогу, а змінна gameIsDone набуває
значення True.
106. if foundAllLetters:
107. print ('ТАК! Секретне слово – "' + secretWord + '"! Ви
вгадали!')
108. gameIsDone = True
Обробка помилкових припущень. У рядку 109 починається
блок else.
109. else:
110. missedLetters = missedLetters + guess
Нагадаємо, що код цього блоку виконується, якщо умова ци-
клу for – логічне "ні" ("хиба"). Проте яка умова? Щоб дізнатися
це, наведіть курсор на початок ключового слова else і переміс-
152
тіть його вгору. Ви побачите, що ключове слово else розташову-
ється в тій самій позиції, що if, у рядку 97.
97. if guess in secretWord:
–пробіл–
109. else:
110. missedLetters = missedLetters + guess
Таким чином, якщо умова в рядку 97 (guess in secretWord)
"хиба", то виконується блок else.
Неправильно вгадані літери додаються в рядкову змінну
missedLetters у рядку 110. Це відбувається так само, як у рядку
98 з літерами, що вгадані правильно.
Перевірка факту програшу гравця. Кожного разу, коли
гравець уводить неправильну літеру, вона додається в змінну
missedLetters. Таким чином, довжина значення змінної
missedLetters (або в коді – len (missedLetters)) стає рівною кіль-
кості помилкових припущень.
112. # Перевіряє перевищення гравцем ліміту спроб і факту
програшу.
113. if len (missedLetters) == len (HANGMAN_PICS) – 1:
114. displayBoard (missedLetters, correctLetters, secretWord)
115. print ('Ви вичерпали всі спроби! \ N не вгадано літер:' +
str (len (missedLetters)) + 'і вгадано
літер: '+ str (len (correctLetters)) +'. Було загадане слово
"'+ secretWord +'". ')
116. gameIsDone = True
Змінна HANGMAN_PICS містить сім рядків із ASCII-
символами рисунку. Отже, якщо довжина рядка missedLetters
дорівнює len (HANGMAN_PICS) – 1 (тобто 6), то гравець виче-
рпав ліміт припущень. Якщо рисунок повішеного завершено, то
гравець програв. Нагадаємо, що HANGMAN_PICS [0] – перший
елемент списку, а HANGMAN_PICS [6] – останній.
Код у рядку 115 виводить секретне слово, а рядок 116 при-
своює змінній gameIsDone значення True.
118. # Запитує, чи хоче гравець зіграти заново (тільки якщо
гра завершена).
119. if gameIsDone:

153
120. if playAgain():
121. missedLetters = ''
122. correctLetters = ''
123. gameIsDone = False
124. secretWord = getRandomWord(words)
Завершення або перезавантаження гри. Незалежно від пе-
ремоги або програшу гра має запитати гравця, чи хоче він зігра-
ти знову. Функція playAgain () обробляє відповідь ("Так" або
"Ні") урядку 120.
Якщо гравець хоче почати гру заново, то значення змінних
missedLetters і correctLetters треба обнулити, змінній gameIsDone
присвоїти значення False і вибрати нове секретне слово. Коли
виконання циклу while повертається до початку, до рядка 91,
ігровий інтерфейс, який відображається на екрані, перезаванта-
жується і готовий до нової гри.
Якщо гравець не ввів нічого, що починається з літери "т", у
відповідь на запит, чи хоче він зіграти заново, то умова в рядку
120 стає хибною і виконується блок else.
125. else:
126. break
Інструкція break зумовлює виконання першої інструкції пі-
сля циклу. Однак оскільки після циклу немає виконуваних ін-
струкцій, то відбувається завершення роботи програми.
Можливі доопрацювання гри. Зігравши у гру "Шибениця"
кілька разів, ви, можливо, подумали, що шести спроб недостат-
ньо для вгадування багатьох слів. Кількість спроб можна легко
збільшити, додавши рядки у список HANGMAN_PICS.
Збережіть файл hangman.py під ім'ям hangman2.py. Для роз-
ширення списку, що містить ASCII-символи для прорисовуван-
ня повішеника, додайте такі рядки, починаючи з 37-го:
37. ===''', '''
38. +–-+
39. [O |
40. /|\ |
41. / \ |
42. ===''', '''

154
43. +–-+
44. [O] |
45. /|\ |
46. / \ |
47. ===''']
Цей код додає два нові багаторядкові значення до списку
HANGMAN_PICS, один рядок служить для прорисовування
лівого вуха повішеника (виділено червоним кольором – рядок
39), інший – для прорисовування обох вух (рядок 44). Програма
повідомляє гравцеві про його програші на підставі значення
виразу len (missedLetters) == len (HANGMAN_PICS) – 1, тому в
інших змінах необхідності немає. Інша частина програми буде
прекрасно працювати з новим списком.
Словники. У першій версії програми "Шибениця" ми вико-
ристовували список з назвами тварин, але можна змінити список
слів у рядку 48. Наприклад, замість тварин можна використову-
вати кольори:
48. words = 'червоний помаранчевий жовтий зелений синій
блакитний фіолетовий білий чорний коричневий'.split ()
або геометричні фігури:
48. words = 'квадрат трикутник прямокутник коло еліпс ромб
трапеція паралелограм п'ятикутник шестикутник восьмикут-
ник'.split ()
або фрукти:
48. words = 'яблуко апельсин лимон лайм груша мандарин
виноград грейпфрут персик банан абрикос манго банан некта-
рин'.split ()
Можна доопрацювати код так, що гра "Шибениця" буде ви-
користовувати кілька наборів слів (назви тварин, кольорів, фі-
гур, фруктів тощо). Програма повідомлятиме гравцеві, який
набір використано для вибору секретного слова. Щоб це зроби-
ти, знадобиться новий тип даних, який називається словником.
Словник, як і список, – це набір значень, але замість доступу до
елементів за цілочисловими індексами в словнику можливий
доступ до елементів за індексами довільного типу. У словнику
ці індекси називаються ключами.
155
Відмінності між списком і словником. При роботі зі словни-
ком застосовуються фігурні дужки {} замість квадратних []. Ще
одна відмінність словника від списку – можливість використання як
ключів значень довільного типу, а не тільки цілих чисел. Нагадає-
мо, що 0 та '0' – різні величини, тобто це будуть різні ключі.
Словники, подібно спискам, можна обробляти циклічно, ви-
користовуючи ключі в циклі for.
Ключі та значення можуть виводитися в різному порядку, тому
що словники, на відміну від списків, не впорядковані (не ранжова-
ні). Першим елементом списку з ім'ям listStuff буде listStuf [0]. Од-
нак у словнику немає першого елемента, тому що немає певного
порядку розташування елементів. У наведеному коді Python просто
вибрав той порядок, у якому елементи зберігалися в пам'яті, і не-
має жодних гарантій, що наступного разу порядок буде тим самим.
Словники не впорядковані та вважаються еквівалентними,
якщо складаються з однакових пар "ключ – значення", а списки
з однаковими значеннями елементів, але різним порядком їхньо-
го розташування, еквівалентними не будуть.
Методи словника keys () та values (). Словники містять два
корисні методи – keys () та values (). Ці методи повертають зна-
чення, типи яких називаються dict_keys та dict_values, відповід-
но. Як і більшість ранжируваних об'єктів, дані цих типів повер-
таються у формі списку функцією list (). Використовуючи мето-
ди keys () або values () у функції len (), можна отримати список
ключів або значень словника.
Використання словника слів у грі "Шибениця". Змінимо
код у новій версії гри "Шибениця", додавши підтримку різних
наборів секретних слів. По-перше, замінимо значення змінної
words, створивши словник, у якому ключі зображені рядками, а
значення – списками рядків. Рядковий метод split () поверне
список рядків, по одному слову в кожному рядку.
48. words = { 'Кольори': 'червоний помаранчевий жовтий зеле-
ний синій блакитний фіолетовий білий чорний коричневий'.split (),
49. 'Фігури': 'квадрат трикутник прямокутник коло еліпс ромб тра-
пеція паралелограм п'ятикутник шестикутник восьмикутник'.split (),
50. 'Фрукти': 'яблуко апельсин лимон лайм груша мандарин вино-
град грейпфрут персик банан абрикос манго банан нектарин'.split (),

156
51. 'Тварини': 'лелека бабуїн баран борсук бик вовк зебра кит
коза корова кішка кролик щур лев лисиця лось ведмідь мул ми-
ша норка носоріг мавпа вівця олень віслюк панда пума скунс
собака сова тигр тюлень тхір ящірка'.split ()}
Рядки 48–51 – це одна інструкція присвоювання. Інструкція
закінчується фігурною дужкою, що закривається в рядку 51.
Випадковий вибір зі списку. Функція choice () модуля
random приймає як аргумент список і повертає з нього випадко-
ве значення. Це схоже на те, що раніше робила функція
getRandomWord (). У новій версії функції getRandomWord ()
буде використана функція choice (). Подібно до того, як функція
randint () повертає випадкове ціле число, функція choice () пове-
ртає випадкове значення зі списку.
Зміни функції getRandomWord () такі, що тепер її параметр – сло-
вник, що складається зі списків рядків, а не просто список рядків.
Так виглядала оригінальна функція:
40. def getRandomWord (wordList):
41. # Ця функція повертає випадковий рядок з переданого списку.
42. wordIndex = random.randint (0, len (wordList) – 1)
43. return wordList [wordIndex]
А так виглядає її код після зміни:
53. def getRandomWord (wordDict):
54. # Ця функція повертає випадковий рядок з переданого
словника списків рядків, а також ключ.
55. # По-перше, випадковим чином вибираємо ключ зі словника:
56. wordKey = random.choice (list (wordDict.keys ()))
57.
58. # По-друге, випадковим чином вибираємо слово зі списку
ключів у словнику:
59. wordIndex = random.randint (0, len (wordDict [wordKey]) – 1)
Ім'я wordList змінено на wordDict для більшої наочності.
Тепер замість випадкового вибору слова зі списку рядків споча-
тку випадково вибирається ключ зі словника wordDict за допомо-
гою виклику random.choice (), а замість повернення рядка wordList
[wordIndex] функція повертає список із двома елементами:
перший елемент – wordDict [wordKey] [wordIndex];
другий елемент – wordKey.

157
Вираз wordDict [wordKey] [wordIndex] у рядку 61 може зда-
ватися складним, але результат його дії зрозуміти дуже просто.
По-перше, уявімо, що змінній wordKey присвоєно значення
'Фрукти', а змінній wordIndex – значення 5. Тоді перетворення
wordDict [wordKey] [wordIndex] матиме такий вигляд:
wordDict[wordKey][wordIndex]
wordDict['Fruits'][wordIndex]
['apple', 'orange', 'lemon', 'lime', 'pear', 'watermelon', 'grape',
'grapefruit', 'cherry', 'banana', 'cantaloupe', 'mango', 'strawberry',
'tomato'][wordIndex]['apple', 'orange', 'lemon', 'lime', 'pear',
'watermelon', 'grape', 'grapefruit', 'cherry', 'banana', 'cantaloupe',
'mango', 'strawberry', 'tomato'][5]
'watermelon'
'Фрукти'
'мандарин'
['яблуко', 'апельсин', 'лимон', 'лайм', 'груша', 'мандарин', 'вино-
град', 'грейпфрут', 'персик', 'банан', 'абрикос', 'манго', 'банан',
'нектарин'][wordIndex]
['яблуко', 'апельсин', 'лимон', 'лайм', 'груша', 'мандарин', 'вино-
град', 'грейпфрут', 'персик', 'банан', 'абрикос', 'манго', 'банан',
'нектарин'][5]
У цьому випадку елемент списку, що повертається функцією, –
це рядок 'мандарин' (нагадаємо, що індекси починаються з 0, тому
індекс [5] відсилає до шостого елемента списку, а не до п'ятого).
Оскільки функція getRandomWord () тепер повертає список із
двох елементів, а не рядок, то і значення змінної secretWord буде
списком, а не рядком. Щоб зберегти ці значення у двох різних
змінних, можна використовувати множинне присвоювання, яке
розглянемо далі.
Видалення елементів списку. Інструкція del видаляє елеме-
нти із зазначеними індексами зі списку. Оскільки del – інструк-
ція, а не функція і не оператор, то вона вказується без круглих
дужок і не має значення, що повертається.
Зазначимо, що коли видаляється елемент з індексом 1, елемент,
який мав індекс 2, стає елементом з індексом 1, а елемент, який мав
індекс 3, отримує індекс 2 і т. д. Вищеописана процедура видаляє
тільки один елемент і, відповідно, зменшує кількість індексів.
158
Довжина списку HANGMAN_PICS – це кількість зроблених
гравцем припущень. Видаляючи рядки з цього списку, можна
скорочувати допустиму кількість припущень і, тим самим,
ускладнювати гру.
Додайте такі рядки в код вашої програми між рядком print
('Ш И Б Е Н И Ц Я') і рядком missedLetters = ''.
103. print ('' Ш И Б Е Н И Ц Я ')
104.
105. difficulty = ''
106. while difficulty not in 'ЛСТ':
107. print ('Виберіть рівень складності: Л – Легкий, С – Сере-
дній, В – Важкий')
108. difficulty = input (). Upper ()
109. if difficulty == 'С':
110. del HANGMAN_PICS [8]
111. del HANGMAN_PICS [7]
112. if difficulty == 'Т':
113. del HANGMAN_PICS [8]
114. del HANGMAN_PICS [7]
115. del HANGMAN_PICS [5]
116. del HANGMAN_PICS [3]
117.
118. missedLetters = ''
Цей код видаляє елементи списку HANGMAN_PICS, робля-
чи його коротше, залежно від обраного рівня складності гри.
Чим вище рівень складності, тим більше елементів видаляється
зі списку HANGMAN_PICS, скорочуючи можливу кількість
спроб угадування. Інша частина коду гри "Шибениця" викорис-
товує довжину цього списку для визначення моменту виведення
повідомлення гравцеві про вичерпання кількості спроб.
Множинне присвоювання. Множинне присвоювання – це
можливість призначення кільком змінним різних значень в од-
ному рядку. Для множинного присвоювання запишіть змінні
через кому і надайте їм список значень. Нижче наведено при-
клад запису, уведіть її в інтерактивній оболонці:
>>> fruit, animal, number = ['апельсин', 'кіт', 42]
>>> fruit
159
'апельсин'
>>> animal
'кіт'
>>> number
42
Виконання коду, наведеного в прикладі, рівноцінно виконан-
ню таких операцій присвоювання:
>>> fruit = ['апельсин', 'кіт', 42] [0]
>>> animal = ['апельсин', 'кіт', 42] [1]
>>> number = ['апельсин', 'кіт', 42] [2]
Кількість рядків з іменами змінних у лівій частині інструкції
присвоювання має бути еквівалентна кількості елементів, зазна-
чених у правій частині. Python автоматично присвоїть значення
першого елемента списку першій змінній, другій змінній – зна-
чення другого елемента і т. д. Якщо кількість змінних буде від-
мінною від кількості елементів списку, то Python інтерпретує це
як помилку.
Зміна рядків 120 та 157 у коді гри "Шибениця" для викорис-
тання множинного присвоювання значень, що повертаються
функцією getRandomWord (), має такий вигляд:
119. correctLetters = ''
120. secretWord, secretSet = getRandomWord(words)
121. gameIsDone = False
–пробіл–
156. gameIsDone = False
157. secretWord, secretSet = getRandomWord(words)
158. else:
159. break
У рядку 120 значення, що повертаються функцією
getRandomWord (word), присвоюються двом змінним –
secretWord і secretSet. Код у рядку 157 робить те саме, коли гра-
вець вирішує зіграти заново.
Вибір гравцем категорії слів. Остання зміна, яку необхідно
зробити, – повідомити гравцеві, до якого набору належить слово
для вгадування (тварина, колір, фігура або фрукт). Нижче пока-
заний початковий код:
160
91. while True:
92. displayBoard(missedLetters, correctLetters, secretWord)
У новій версії додайте рядок 124, щоб код був таким:
123. while True:
124. print('Секретне слово з набору: ' + secretSet)
125. displayBoard(missedLetters, correctLetters, secretWord)
На цьому зміни в програмі "Шибениця" закінчуються. За-
мість простого списку рядкових змінних секретне слово вибира-
ється з кількох різних списків. Програма повідомляє гравцеві,
який набір слів використаний для вибору секретного слова.
Спробуйте зіграти в новій версії.
Ви можете легко змінити словник слів, що починається в ря-
дку 48, щоб ще збільшити кількість наборів слів.
Отже, написання коду гри "Шибениця" завершено. Ви ви-
вчили кілька нових понять, додаючи при цьому в гру нові мож-
ливості. Навіть написавши програму, ви можете розширювати її
функціонал у процесі вивчення Python.
Словники схожі на списки, за винятком того, що словники
можуть використовувати індекси довільного типу, а не тільки
цілочислові значення. Індекси словників називаються ключами.
Багатозначне присвоювання – це раціональний спосіб присвою-
вання різним змінним значень, поміщених у список.
Гра "Шибениця" стала значно довершенішою порівняно з
попередньою версією. Тепер вам знайомі основні концептуальні
поняття, необхідні при написанні програм: змінні, цикли, функ-
ції, типи даних, список і словник.

1.15.2. Проект № 2. Гра "Пінг-Понг"

Розробимо гру зі стрибаючим м'ячем і ракеткою (рис. 1.9).


М'яч буде літати по екрану, а гравець – відбивати його ракет-
кою. Якщо м'яч торкнеться нижньої границі екрана, то гра заве-
ршиться.

161
Рис. 1.9

Хоча на перший погляд гра досить проста, її код буде склад-


нішим порівняно з усім, що ви досі писали, оскільки програма
має виконувати безліч різних дій. Наприклад, треба анімувати
ракетку і м'яч, а також обробляти зіткнення м'яча з ракеткою й
межами ігрового поля.
Створюємо ігрове полотно. Насамперед відкриємо новий
файл в оболонці Anaconda. Потім імпортуємо tkinter і створимо
полотно для рисування:
from tkinter import *
import random
import time
tk = Tk()
tk.title("Гра")
tk.resizable(0, 0)
tk.wm_attributes("-topmost", 1)
canvas = Canvas(tk, width=500, height=400, bd=0,
highlightthickness=0)
canvas.pack()
tk.update()

Спочатку крім tkinter імпортуємо модулі time та random,


(import random та import time) – вони нам знадобляться трохи
пізніше.

162
Викликом tk.title ("Гра") задаємо заголовок ігрового вікна
(для цього служить функція title раніше створеного об'єкта tk).
Викликаємо функцію resizable, щоб зробити розмір вікна фіксо-
ваним. Аргументи 0, 0 означають: розмір вікна має бути незмін-
ним як по горизонталі, так і по вертикалі. Викликаємо функцію
wm_attributes, указуючи, що вікно з полотном треба розмістити
зверху над усіма іншими вікнами ("-topmost").
Зверніть увагу, що, створюючи полотно, ми передали у фун-
кцію більше іменованих аргументів, ніж у попередніх прикла-
дах. Наприклад, аргументи bd = 0 та highlightthickness = 0 потрі-
бні для того, щоб навколо полотна не було рамки (від цього на-
ша гра матиме кращий вигляд).
У результаті виклику canvas.pack () полотно змінить розмір від-
повідно до значень ширини та висоти, зазначених у попередньому
рядку коду. Команда tk.update () готує tkinter до ігрової анімації.
Без виклику update програма не працюватиме як задумано.
Не забувайте зберігати код у процесі його написання. При
першому зберіганні дайте файлу ім'я, наприклад paddleball.py.
Створюємо клас для м'яча. Щоб створити клас для м'яча,
насамперед додамо до нього код відтворення м'яча на полотні,
для чого треба:
• створити клас під назвою Ball, який приймає як аргументи
функції __init__ полотно і колір м'яча;
• зберегти у властивості об'єкта полотно, щоб надалі рисува-
ти на ньому м'яч;
• зобразити на полотні коло, заповнене переданим у аргумен-
ті кольором;
• зберегти ідентифікатор, який поверне функція рисування кола,
оскільки за його допомогою ми будемо переміщати м'яч по екрану;
• перемістити нарисоване коло в центр полотна.
Цей код треба додати в початок файлу, після рядка import time:
from tkinter import *
import random
import time
1 class Ball:
2 def __init__(self, canvas, color):
3 self.canvas = canvas
163
4 self.id = canvas.create_oval(10, 10, 25, 25, fill=color)
5 self.canvas.move(self.id, 245, 100)
def draw(self):
pass
У рядку 1 даємо класу ім'я Ball. У рядку 2 створюємо функ-
цію ініціалізації __init__, яка приймає як аргументи полотно і
колір. У рядку 3 зберігаємо аргумент canvas у властивості з та-
ким самим ім'ям.
У рядку 4 викликаємо функцію create_oval для рисування ко-
ла, передаючи їй п'ять аргументів: x- та y-координати лівого
верхнього кута прямокутника (10 і 10), x- та y-координати його
правого нижнього кута (25 і 25) і колір заповнення.
Функція create_oval повертає ідентифікатор нарисованої фі-
гури, який ми зберігаємо у властивості id. У рядку 5 переміщає-
мо коло приблизно в центр полотна (позиція 245, 100), передав-
ши для цього у функцію move збережений раніше ідентифікатор
фігури (властивість id).
У двох останніх рядках визначаємо функцію draw (def draw
(self)), указуючи замість її тіла ключове слово pass (поки функ-
ція draw нічого не робить, але скоро ми це виправимо).
Отже, у нас є клас Ball, і тепер треба створити його об'єкт
(згадайте: клас лише описує, що треба робити, а об'єкт виконує
конкретні дії). Щоб створити об'єкт – м'яч червоного кольору –
додайте в кінець програми такий код:
ball = Ball(canvas, 'red')
Якщо тепер запустити програму, то полотно на мить з'явиться
і знову зникне. Щоб вікно не закривалося, треба додати до про-
грами цикл ігрової анімації, який називають головним циклом гри.
Головний цикл – центральний елемент програми, який керує бі-
льшою часткою дій. Спочатку наш головний цикл буде тільки пе-
рерисовувати екран. Цей цикл нескінченний (принаймні він буде
виконуватися до тих пір, поки ми не закриємо вікно), і при кожно-
му його повторі ми будемо давати команду перерисувати екран і
робити паузу на одну соту частку секунди. Додайте цей код у кі-
нець програми:
ball = Ball(canvas, 'red')
164
while 1:
tk.update_idletasks()
tk.update()
time.sleep(0.01)
Якщо тепер запустити код, то приблизно в центрі полотна
має з'явитися м'яч (рис. 1.10).

Рис. 1.10

Клас Ball підготовлений, тепер анімуємо м'яч. Наше завдання


– зробити так, щоб він рухався і відскакував від перешкод, змі-
нюючи напрямок.
Переміщення м'яча. Щоб м'яч рухався, насамперед змінимо
функцію draw таким чином:
class Ball:
def __init__(self, canvas, color):
self.canvas = canvas
self.id = canvas.create_oval(10, 10, 25, 25, fill=color)
self.canvas.move(self.id, 245, 100)
def draw(self):
self.canvas.move(self.id, 0, -1)
Оскільки у функції __init__ ми зберегли полотно у властиво-
сті canvas, то тепер для виклику функції move можна звертатися
до полотна через self.canvas.
165
Передаємо функції move три аргументи: збережений раніше
ідентифікатор кола (м'ячі), а також числа 0 та -1. Тут 0 означає,
що переміщення по горизонталі не має бути, а -1 – що м'яч має
переміститися на 1 піксель угору.
Ми внесли цю невелику зміну, щоб подивитися, як перемі-
щається м'яч. У процесі створення програми бажано частіше
перевіряти її частини. Тільки уявіть, що ми написали всю про-
граму й раптом виявили, що вона не працює. Буде нелегко зро-
зуміти, у яку саме частину коду вкралася помилка.
У головний цикл гри треба ввести ще одну змінну. Додаємо в
тіло циклу while (це і є головний цикл) виклик функції об'єкта
м'яча draw:
while 1:
ball.draw()
tk.update_idletasks()
tk.update()
time.sleep(0.01)
Якщо тепер запустити код, то м'яч почне рухатися вгору і не-
забаром зникне за межею вікна, оскільки головний цикл швидко
оновлює екран, постійно перерисовуючи зображення на ньому
за допомогою команд update_idletasks та update.
Команда time.sleep – це виклик функції sleep з модуля time.
Вона зупиняє виконання коду, у нашому випадку – на соту част-
ку секунди (0.01). Це треба для того, щоб програма працювала
не надто швидко, інакше м'яч зникне раніше, ніж ми встигнемо
його розгледіти.
Отже, зараз наш головний цикл трохи зрушує м'яч, перерисо-
вує екран (після чого нове положення м'яча стає видимим), ро-
бить невелику паузу і повторює ці дії знову і знову.
Примітка. При закритті вікна гри у вікні оболонки можуть
виникнути повідомлення про помилки. Справа в тому, що коли
вікно закрито, дії з ним і його змістом (що виконуються в циклі)
неможливі. Це і стає причиною повідомлень.
Тепер код гри має такий вигляд:
from tkinter import *
import random
import time
сlass Ball:
def __init__(self, canvas, color):
166
self.canvas = canvas
self.id = canvas.create_oval(10, 10, 25, 25, fill=color)
self.canvas.move(self.id, 245, 100)
def draw(self):
self.canvas.move(self.id, 0, -1)
tk = Tk()
tk.title("Гра")
tk.resizable(0, 0)
tk.wm_attributes("-topmost", 1)
canvas = Canvas(tk, width=500, height=400, bd=0,
highlightthickness=0)
canvas.pack()
tk.update()
ball = Ball(canvas, 'red')
while 1:
ball.draw()
tk.update_idletasks()
tk.update()
time.sleep(0.01)
Відскоки м'яча. М'яч, який відлітає за межі полотна, не дуже
підходить для цієї гри, тому давайте "навчимо" його відскакува-
ти від меж. Створимо у функції __init__ класу Ball ще кілька
властивостей:
def __init__(self, canvas, color):
self.canvas = canvas
self.id = canvas.create_oval(10, 10, 25, 25, fill=color)
self.canvas.move(self.id, 245, 100)
self.x = 0
self.y = -1
self.canvas_height = self.canvas.winfo_height()
Ми додали ще три рядки коду. Команда self.x = 0 задає влас-
тивості self об'єкта з ім'ям x значення 0, а self.y = -1 – властивос-
ті self об'єкта y значення -1. Зберігаємо у властивості
canvas_height значення, отримане з функції полотна winfo_height
(функція повертає поточну висоту полотна).
167
Знову змінюємо функцію draw:
def draw(self):
1 self.canvas.move(self.id, self.x, self.y)
2 pos = self.canvas.coords(self.id)
3 if pos[1] <= 0:
self.y = 1
4 if pos[3] >= self.canvas_height:
self.y = -1
У рядку 1 ми поміняли виклик функції move, тепер передаємо
в неї властивості x та y. У рядку 2 створили змінну pos, помісти-
вши в неї значення, отримане від функції полотна coords. Ця фун-
кція повертає x- та y-координати будь-якої фігури на полотні за її
ідентифікатором. У даному випадку ми передаємо у функцію
coords властивість id, де зберігається ідентифікатор кола (м'яча).
Функція coords повертає координати у вигляді списку з чотирьох
чисел. Якщо вивести його на екран, то побачимо щось подібне:
print(self.canvas.coords(self.id))
[255.0, 29.0, 270.0, 44.0]
Перші два числа (255.0 та 29.0) – це координати лівого верхнього
кута прямокутника, у який вписане наше коло (x1 та y1), а другі два
(270.0 та 44.0) – координати правого нижнього кута прямокутника (x2
та y2). Цими значеннями ми скористаємося в наступних рядках коду.
У рядку 3 порівнюємо координату y1 (верх м'яча) з нулем:
якщо вона менше або дорівнює 0, то задаємо властивості y зна-
чення 1. Це означає, що якщо м'яч досяг верхньої границі поло-
тна, то треба припинити його рух угору, переставши віднімати 1
з його вертикальної координати. Замість цього будемо додавати
до цієї координати 1, щоб м'яч змінив напрямок.
У рядку 4 порівнюємо координату y2 (низ м'яча) із властивіс-
тю canvas_height: якщо y2 більше або дорівнює canvas_height, то
знову ставимо властивості y значення -1.
Запустіть цей код, і побачите, як м'яч літає вгору-вниз, від-
скакуючи від меж, поки ви не закриєте вікно.
Міняємо початковий напрямок руху м'яча. М'яча, який
рухається то вгору, то вниз, для нашої гри недостатньо, тому
додамо різноманітності, помінявши початковий напрямок
польоту м'яча, тобто кут його руху, після запуску гри. Замініть у
функції __init__ рядки
168
self.x = 0
self.y = -1
Замість них уведіть такий код (зверніть увагу на кількість
пробілів на початку кожного рядка – їх має бути вісім):
1 starts = [-3, -2, -1, 1, 2, 3]
2 random.shuffle(starts)
3 self.x = starts[0]
4 self.y = -3
У рядку 1 ми створили змінну starts, помістивши туди список
із шести чисел. У рядку 2 перемішали елементи списку за допо-
могою random.shuffle. У рядку 3 помістили у властивість x зна-
чення першого елемента списку. Тепер до x може потрапити
будь-яке значення з вихідного списку, від -3 до 3.
У рядку 4 поміщаємо у властивість y значення -3 (щоб при-
скорити рух м'яча). Потім вносимо кілька правок, щоб м'яч не
летів за бічні границі полотна. Додамо в кінець функції __init__
наступний рядок коду, який, отримавши від функції winfo_width
ширину полотна, зберігає її у властивості canvas_width:
self.canvas_width = self.canvas.winfo_width()
Ця нова властивість потрібна у функції draw для перевірки
факту досягнення м'ячем правої границі полотна:
if pos[0] <= 0:
self.x = 3
if pos[2] >= self.canvas_width:
self.x = -3
Оскільки при досягненні границь ми задаємо властивості x
значення 3 або -3, то будемо використовувати такі самі значення
і для y, щоб м'яч завжди рухався з однаковою швидкістю. У ре-
зультаті функція draw набуде вигляду:
def draw(self):
self.canvas.move(self.id, self.x, self.y)
pos = self.canvas.coords(self.id)
if pos[1] <= 0:
self.y = 3
if pos[3] >= self.canvas_height:
self.y = -3
169
if pos[0] <= 0:
self.x = 3
if pos[2] >= self.canvas_width:
self.x = -3
Збережемо код програми й запустимо її. М'яч буде переміщатися
по полотну, відскакуючи від його границь. Програма стане такою:
from tkinter import *
import random
import time
class Ball:
def __init__(self, canvas, color):
self.canvas = canvas
self.id = canvas.create_oval(10, 10, 25, 25, fill=color)
self.canvas.move(self.id, 245, 100)
starts = [-3, -2, -1, 1, 2, 3]
random.shuffle(starts)
self.x = starts[0]
self.y = -3
self.canvas_height = self.canvas.winfo_height()
self.canvas_width = self.canvas.winfo_width()
def draw(self):
self.canvas.move(self.id, self.x, self.y)
pos = self.canvas.coords(self.id)
if pos[1] <= 0:
self.y = 3
if pos[3] >= self.canvas_height:
self.y = -3
if pos[0] <= 0:
self.x = 3
if pos[2] >= self.canvas_width:
self.x = -3
tk = Tk()
tk.title("Игра")
tk.resizable(0, 0)

170
tk.wm_attributes("-topmost", 1)canvas = Canvas(tk, width=500,
height=400, bd=0,
highlightthickness=0)
canvas.pack()
tk.update()
ball = Ball(canvas, 'red')
while 1:
ball.draw()
tk.update_idletasks()
tk.update()
time.sleep(0.01)
Створюємо ракетку. Почнемо з класу для ракетки (Paddle), код
якого треба ввести відразу після класу Ball і його функції draw:
def draw(self):
self.canvas.move(self.id, self.x, self.y)
pos = self.canvas.coords(self.id)
if pos[1] <= 0:
self.y = 3
if pos[3] >= self.canvas_height:
self.y = -3
if pos[0] <= 0: self.x = 3
if pos[2] >= self.canvas_width:
self.x = -3
class Paddle:
def __init__(self, canvas, color):
self.canvas = canvas
self.id = canvas.create_rectangle(0, 0, 100, 10,
fill=color)
self.canvas.move(self.id, 200, 300)
def draw(self):
pass
Цей код дуже схожий на код класу Ball. Однак замість функ-
ції create_oval ми використовуємо create_rectangle і переміщаємо
нарисований нею прямокутник у позицію 200, 300 (200 пікселів
від лівого краю полотна і 300 пікселів – від верхнього краю).
171
Ближче до кінця програми створимо об'єкт класу Paddle і до-
повнимо ігровий цикл викликом функції draw цього об'єкта:
paddle = Paddle(canvas, 'blue')
ball = Ball(canvas, 'red')
while 1:
ball.draw()
paddle.draw()
tk.update_idletasks()
tk.update()
time.sleep(0.01)
Якщо тепер запустити гру, то побачимо літаючий м'яч і роз-
міщену на одному місці прямокутну ракетку (рис. 1.11).

Рис. 1.11

Керування ракеткою. Щоб керувати ракеткою, рухаючи її


праворуч і ліворуч, скористаємося прив'язкою до подій: прив'я-
жемо клавіші-стрілки "праворуч" і "ліворуч" до викликів функ-
цій класу Paddle. При натисканні стрілки "ліворуч" будемо зада-
вати властивості x об'єкта-ракетки значення -2 (для пересування
ліворуч), а при натисканні стрілки "праворуч" – значення 2 (для
пересування праворуч).
У функції __init__ класу Paddle створимо властивості x і
canvas_width для зберігання ширини полотна (так само як ми
робили для класу Ball):
def __init__(self, canvas, color):
172
self.canvas = canvas
self.id = canvas.create_rectangle(0, 0, 100, 10, fill=color)
self.canvas.move(self.id, 200, 300)
self.x = 0
self.canvas_width = self.canvas.winfo_width()
Додамо до класу Paddle дві функції: для зміни напрямку лі-
воруч (turn_left) і праворуч (turn_right). Помістимо їхні коди
відразу після функції draw:
def turn_left(self, evt):
self.x = -2
def turn_right(self, evt):
self.x = 2
Для прив'язки цих функцій до натискань потрібних клавіш до-
дамо у функцію __init__ класу Paddle ще два рядки коду. У нашому
випадку треба прив'язати функцію turn_left до натискання клавіші-
стрілки "ліворуч" (подія з ім'ям '<KeyPress-Left>'), а функцію
turn_right – до клавіші-стрілки "праворуч" (подія '<KeyPress-
Right>'). Після доопрацювання функція __init__ матиме вигляд:
def __init__(self, canvas, color):
self.canvas = canvas
self.id = canvas.create_rectangle(0, 0, 100, 10,
fill=color)self.canvas.move(self.id, 200, 300)
self.x = 0
self.canvas_width = self.canvas.winfo_width()
self.canvas.bind_all('<KeyPress-Left>', self.turn_left)
self.canvas.bind_all('<KeyPress-Right>', self.turn_right)
Код функції draw класу Paddle буде приблизно таким самим,
як для класу Ball:
def draw(self):
self.canvas.move(self.id, self.x, 0)
pos = self.canvas.coords(self.id)
if pos[0] <= 0:
self.x = 0
elif pos[2] >= self.canvas_width:
self.x = 0

173
Для переміщення ракетки відповідно до значення властивості x
використовуємо функцію move – self.canvas.move (self.id, self.x, 0).
Отримуємо координати ракетки (зберігаючи їх у змінній pos), щоб
перевірити, чи досягла ракетка лівої або правої границі полотна.
На відміну від м'яча, ракетка при зіткненні з границею має не ві-
дскочити від неї, а зупинитися. Тому, якщо ліва x-координата (pos
[0]) менше або дорівнює 0 (<= 0), то обнуляємо властивість x (self.x
= 0). Якщо права x-координата ракетки (pos [2]) більше або дорів-
нює ширині полотна (> = self.canvas_width), також обнуляємо x.
Примітка. Якщо ви запустите гру зараз, то, можливо, дове-
деться кликнути по її вікну, щоб програма почала реагувати на
натискання клавіш. Клик робить вікно активним, після чого
воно може обробляти події клавіатури.
Перевірка на зіткнення м'яча з ракеткою. Зараз наш код вла-
штований так, що м'яч не стикається з ракеткою, а пролітає крізь неї.
Щоб цього не відбувалося, м'яч має "знати" про зіткнення з ракет-
кою так само, як він "знає" про зіткнення з границями полотна.
Проблему можна розв'язати, додавши відповідний код у функ-
цію draw (де виконується перевірка зіткнень із границями). Однак
краще створити окремі функції, щоб програма складалася з неве-
ликих частин. Справа в тому, що в програмі дуже складно розібра-
тися, якщо в одному місці (наприклад в одній функції) накопичу-
ється занадто багато рядків коду. Унесемо необхідні зміни.
Додамо у функцію __init__ класу Ball ще один аргумент –
об'єкт-ракетку:
class Ball:
1def __init__(self, canvas, paddle, color):
self.canvas = canvas
2self.paddle = paddle
self.id = canvas.create_oval(10, 10, 25, 25, fill=color)
self.canvas.move(self.id, 245, 100)
starts = [-3, -2, -1, 1, 2, 3]
random.shuffle(starts)
self.x = starts[0]
self.y = -3
self.canvas_height = self.canvas.winfo_height()
self.canvas_width = self.canvas.winfo_width()

174
Зверніть увагу, що в рядку 1 ми змінили аргументи __init__,
додавши до них ракетку (paddle), а в рядку 2 зберегли значення
аргументу paddle у властивості з такою самою назвою.
Тепер треба змінити код створення об'єкта-м'яча з урахуван-
ням нового аргументу – ракетки. Цей код міститься в кінці про-
грами перед головним циклом:
paddle = Paddle(canvas, 'blue')
ball = Ball(canvas, paddle, 'red')
while 1:
ball.draw()
paddle.draw()
tk.update_idletasks()
tk.update()
time.sleep(0.01)
Код для перевірки зіткнення з ракеткою буде складнішим,
ніж для перевірки границь полотна. Помістимо його в нову фу-
нкцію hit_paddle, додавши її виклик у функцію draw класу Ball,
разом з перевіркою на зіткнення з нижньою границею:
def draw(self):
self.canvas.move(self.id, self.x, self.y)
pos = self.canvas.coords(self.id)
if pos[1] <= 0:
self.y = 3
if pos[3] >= self.canvas_height:
self.y = -3
if self.hit_paddle(pos) == True:
self.y = -3
if pos[0] <= 0:
self.x = 3
if pos[2] >= self.canvas_width:
self.x = -3
Якщо hit_paddle повертає True, то ми змінюємо напрямок
польоту м'яча, задаючи властивості y значенні -3 (self.y = -3). Не
намагайтеся зараз запустити гру – ми ще не створили функцію
hit_paddle. Давайте це виправимо.
Додайте код функції hit_paddle відразу перед функцією draw:
1 def hit_paddle(self, pos):
175
2 paddle_pos = self.canvas.coords(self.paddle.id)
3 if pos[2] >= paddle_pos[0] and pos[0] <= paddle_pos[2]:
4 if pos[3] >= paddle_pos[1] and pos[3] <=
paddle_pos[3]:
return True
return False
У рядку 1 оголошуємо функцію з аргументом pos, у якому
будемо передавати поточні координати м'яча. У рядку 2 отриму-
ємо координати ракетки та зберігаємо їх у змінній paddle_pos.
У рядку 3 міститься конструкція if, умова якої означає:
x-координата правого боку м'яча більше, ніж x-координата
лівого боку ракетки, а x-координата лівого боку м'яча менше,
ніж x-координата правого боку ракетки. Тут pos [2] відповідає x-
координаті правого боку м'яча, а pos [0] – x-координаті його
лівого боку. При цьому paddle_pos [0] відповідає x-координаті
лівого боку ракетки, а paddle_pos [2] – х-координаті її правого
боку. На рис. 1.12 зображена схема, де показані координати для
випадку, коли м'яч ось-ось торкнеться ракетки.

Рис. 1.12

М'яч летить до ракетки, але його правий бік (pos [2]) ще не


досяг лівого боку ракетки (paddle_pos [0]).
У рядку 4 перевіряємо, чи не міститься нижній бік м'яча (pos
[3]) між верхом ракетки (paddle_pos [1]) та її низом (paddle_pos
[3]). На рис. 1.13 показана ситуація, коли нижній бік м'яча (pos
[3]) ще не досяг верхнього боку ракетки (paddle_pos [1]).
176
Рис. 1.13

Для цього випадку функція hit_paddle поверне значення False.


Примітка. Навіщо перевіряти факт знаходження низу м'яча
між верхом і низом ракетки, якщо можна перевірити факт зітк-
нення низу м'яча з поверхнею ракетки? Справа в тому, що м'яч
переміщується в кожному циклі на 3 пікселя, тому його низ мо-
же перескочити за верх ракетки. Якщо в такій ситуації порівню-
вати тільки низ м'яча і верх ракетки, то перевірка дасть негатив-
ний результат і м'яч полетить далі скрізь ракетку.
Додаємо можливість програшу. Пора зробити з програми з
літаючим м'ячем і ракеткою справжню гру. Для ігор важливий
елемент невизначеності, тобто ймовірність програшу, а зараз
м'яч просто літає по екрану, тобто програти неможливо.
Закінчимо створення гри, написавши код, який зупиняє анімацію,
якщо м'яч торкнеться "землі" (тобто нижньої границі полотна).
Спочатку створимо в тілі функції __init__ класу Ball власти-
вість hit_bottom (ознака того, що м'яч досяг нижньої границі
полотна). Додамо цей код у низ функції __init__:
self.canvas_height = self.canvas.winfo_height()
self.canvas_width = self.canvas.winfo_width()
self.hit_bottom = False
Змінимо головний цикл у кінці програми:
while 1:
if ball.hit_bottom == False:
177
ball.draw()
paddle.draw()
tk.update_idletasks()
tk.update()
time.sleep(0.01)
Тепер на кожному повторі циклу перевіряємо значення
hit_bottom, щоб дізнатися, чи не досяг м'яч нижньої границі по-
лотна. Як видно з умови if, м'яч і ракетка будуть переміщатися,
тільки якщо м'яч не досяг нижньої границі. В іншому випадку
м'яч і ракетка замруть (ми більше не будемо їх анімувати), що
буде означати кінець гри.
Залишилося лише доопрацювати функцію draw класу Ball:
def draw(self):
self.canvas.move(self.id, self.x, self.y)
pos = self.canvas.coords(self.id)
if pos[1] <= 0:
self.y = 3
if pos[3] >= self.canvas_height:
self.hit_bottom = True
if self.hit_paddle(pos) == True:
self.y = -3
if pos[0] <= 0:
self.x = 3
if pos[2] >= self.canvas_width:
self.x = -3
Ми змінили конструкцію if, яка перевіряє, чи не досяг м'яч
нижньої границі полотна: чи не зрівнялася y-координата низу
м'яча з висотою полотна (canvas_height) або не перевищила
вона це значення. Якщо це сталося, то замість зміни властивос-
ті y (зіткнення з нижньою границею полотна більше не має
спричиняти відскок м'яча) задаємо властивості hit_bottom зна-
чення True.
Якщо тепер запустити гру і дозволити м'ячу пролетіти
повз ракетки, то весь рух на екрані має зупинитися, тобто зітк-
нення м'яча з низом ігрового екрана зумовить завершення гри
(рис. 1.14).

178
Рис. 1.14

У результаті код гри матиме такий вигляд (якщо гра не пра-


цює або працює неправильно, то звірте з ним свою програму):
from tkinter import *
import random
import time
class Ball:
def __init__(self, canvas, paddle, color):
self.canvas = canvas
self.paddle = paddle
self.id = canvas.create_oval(10, 10, 25, 25, fill=color)
self.canvas.move(self.id, 245, 100)
starts = [-3, -2, -1, 1, 2, 3]
random.shuffle(starts)
self.x = starts[0]
self.y = -3
self.canvas_height = self.canvas.winfo_height()
self.canvas_width = self.canvas.winfo_width()
self.hit_bottom = False
def hit_paddle(self, pos): paddle_pos =
self.canvas.coords(self.paddle.id)
if pos[2] >= paddle_pos[0] and pos[0] <= paddle_pos[2]:

179
if pos[3] >= paddle_pos[1] and pos[3] <= paddle_pos[3]:
return True
return False
def draw(self):
self.canvas.move(self.id, self.x, self.y)
pos = self.canvas.coords(self.id)
if pos[1] <= 0:
self.y = 3
if pos[3] >= self.canvas_height:
self.hit_bottom = True
if self.hit_paddle(pos) == True:
self.y = -3
if pos[0] <= 0:
self.x = 3
if pos[2] >= self.canvas_width:
self.x = -3
class Paddle:
def __init__(self, canvas, color):
self.canvas = canvas
self.id = canvas.create_rectangle(0, 0, 100, 10, fill=color)
self.canvas.move(self.id, 200, 300)
self.x = 0
self.canvas_width = self.canvas.winfo_width()
self.canvas.bind_all('<KeyPress-Left>', self.turn_left)
self.canvas.bind_all('<KeyPress-Right>', self.turn_right)
def draw(self):
self.canvas.move(self.id, self.x, 0)
pos = self.canvas.coords(self.id)
if pos[0] <= 0:
self.x = 0
elif pos[2] >= self.canvas_width:
self.x = 0
def turn_left(self, evt):
self.x = -2

180
def turn_right(self, evt):
self.x = 2
tk = Tk()
tk.title("Гра")
tk.resizable(0, 0)
tk.wm_attributes("-topmost", 1)
canvas = Canvas(tk, width=500, height=400, bd=0,
highlightthickness=0)
canvas.pack()
tk.update()
paddle = Paddle(canvas, 'blue')
ball = Ball(canvas, paddle, 'red')
while 1:
if ball.hit_bottom == False:
ball.draw()
paddle.draw()
tk.update_idletasks()
tk.update()
time.sleep(0.01)
Можливі вдосконалення гри. Зараз гра украй проста. Щоб
вона мала професійний вигляд, варто її доопрацювати.
1. Затримка перед початком гри. Гра починається одразу після
запуску, проте без клацання по полотну клавіші-стрілки можуть
не розпізнаватися програмою. Спробуйте додати затримку перед
стартом гри, щоб у гравця вистачило часу клацнути по полотну.
Ще краще, скориставшись прив'язкою до подій, зробити так, щоб
гра починалася при натисканні мишкою всередині вікна.
Підказка 1. Ми вже додавали код для прив'язки до подій у клас
ракетки (Paddle), його можна використовувати як відправну точку.
Підказка 2. Для прив'язки до натиснення лівої кнопки мишки
використовуйте ім'я події '<Button-1>'.
2. Екран "Кінець гри". Зараз після закінчення гри екран за-
стигає. Користувачеві буде набагато зручніше, якщо при тор-
канні м'ячиком нижнього краю полотна на екрані з'явиться по-
відомлення "Кінець гри". У цьому вам допоможе функція

181
create_text. Варто звернути увагу й на функцію itemconfig з ар-
гументом state, у якому можна передавати такі значення, як
normal та hidden.
Як додаткове завдання спробуйте витримати перед показом
повідомлення невелику паузу.
3. Прискорення м'яча. Якщо вам доводилося грати в теніс, то
ви знаєте, що часом м'яч відлітає від ракетки швидше, ніж руха-
вся раніше. Це залежить від сили удару по ньому. У нашій грі
м'яч рухається з постійною швидкістю. Доопрацюйте програму
так, щоб швидкість м'яча при відскоку змінювалася залежно від
рухів ракетки.
4. Рахунок у грі. Додайте у гру підрахунок очок. Кожного ра-
зу, коли м'яч відскакує від ракетки, рахунок має зростати. Зро-
біть так, щоб набрані очки відображалися в правому верхньому
куті ігрового екрана. Тут вам знадобиться функція itemconfig.

182
РОЗДІЛ 2
ПРОГРАМУВАННЯ ЗАДАЧ
МОВОЮ PYTHON

2.1. Програмування задач


на числові послідовності
Почнемо з визначення послідовності.
Розглянемо натуральний ряд
1, 2, 3, ..., n, ..., n', ...,
у якому числа розміщені за зростанням, так що більше число n'
іде за меншим числом n (або менше n передує більшому числу
n'). Якщо замінити в такому ряду за яким-небудь правилом кож-
не натуральне число n деяким значущим числом xn , то отримає-
мо числову послідовність
x1, x2, x3, ..., xn, ..., xn', ...,
члени чи елементи xn якої занумеровані всіма натуральними
числами та розміщені за зростанням номерів. При n' > n член xn'
іде за членом xn (xn передує xn' ) незалежно від того, чи буде чис-
ло xn' більше, менше чи навіть дорівнювати числу xn.
Згадаємо також про десяткове наближення (наприклад за не-
достачею) до 2 зі зростаючою точністю; воно набуватиме
послідовних значень:
1,4; 1,41; 1,414; 1,4142; ...
1 2 3 4
Іноді послідовність задається тим, що вказується безпосеред-
ньо вираз для xn. Наприклад, у випадку арифметичної чи геомет-
ричної прогресії маємо відповідно xn = a + d ( n − 1) чи xn = aqn−1.
Використовуючи цей вираз, можна одразу вираховувати будь-
яке значення елемента послідовності за заданим йому номером,
не обчислюючи попередніх значень. В інших випадках може
бути невідомим вираз для спільного члена xn послідовності.
183
Проте послідовність вважається заданою, якщо існує прави-
ло, за яким може бути обчислений будь-який її член, якщо відо-
мий його номер.
Отже, знаючи правило для наближеного обчислення коренів,
ми можемо вважати заданою всю послідовність десяткових на-
ближень до 2 , хоча вираз для її загального члена нам не відо-
мий, тобто послідовність задається словесним описом.
Якщо послідовність у зазначеному сенсі задано, то цим не тіль-
ки охарактеризовано всю множину набутих нею значень загалом,
але й визначено порядок, за яким ці значення набуваються; кожно-
му номеру відповідає власне значення елемента послідовності, а з
двох значень наступним вважається те, номер якого більший.
Зауважимо, що значення елементів послідовності не обов'яз-
ково мають бути різними. Наприклад, якщо задати послідов-
ність однією з формул
1 + ( −1) n
xn = 1; xn = ( −1) n +1; xn = ,
n
то відповідними послідовностями будуть:

елементи: 1, 1, 1, 1, 1, 1, ...
їхні номери: 1, 2, 3, 4, 5, 6, ...
елементи: 1, -1, 1, -1, 1, -1, ...
їхні номери: 1, 2, 3, 4, 5, 6, ...
1 1
елементи: 0, 1, 0, , 0, , ...
2 3
їхні номери: 1, 2, 3, 4, 5, 6, ...

У першому випадку маємо просто постійну величину: уся


множина значень, яких вона може набувати, зводиться до одно-
го. У другому – множина складається із двох значень, 1 або -1,
що набуваються по черзі. У третьому випадку множина значень
нескінченна, але це не заважає значенням елементів через одне
дорівнювати 0; ми вважаємо, що значення 0 на п'ятому місці йде
не тільки за 1 на другому, а й за значенням 0 на першому місці.

184
Ще один спосіб задання послідовності – рекурентна формула.
Згадаємо, що це таке.
Формулу, що виражає будь-який член послідовності, почи-
наючи з деякого, через попередні (один або кілька), називають
рекурентною (від лат. recurro – повертатися).
Підсумовуючи викладене, назвемо три основні способи за-
дання послідовності.
Аналітичний – послідовність задається формулою загального
(або n-го) члена.
Рекурентний – будь-який член послідовності, починаючи з
деякого, виражається через попередні члени. При цьому способі
задання послідовності вказують її перший член або кілька поча-
ткових членів і формулу, що дозволяє визначити будь-який член
послідовності за відомими попередніми членами.
Словесний – задання послідовності описом.
Для складання програм нам частіше доведеться використову-
вати рекурентне співвідношення. Покажемо це на прикладах.
Приклад 2.1. Скласти програму виведення на екран членів
10 n
послідовності, заданої формулою an = .
n!
Для складання програми необхідно перейти від формули n-го
члена, яким задана послідовність, до рекурентного співвідно-
шення, що зв'язує попередній член з наступним, і вказати почат-
кове значення для першого або нульового елемента. Як це зро-
бити? Виявляється, дуже просто!
1. Запишемо, чому буде дорівнювати член послідовності, що
n −1
передує n-му, тобто (n-1)-й елемент послідовності: a n −1 = 10 .
( n − 1)!
2. Тепер поділимо n-й елемент на (n-1)-й, отримаємо:
10 n
an 10 n ⋅ ( n − 1)! 10
= nn!−1 = = .
an −1 10 10 n −1 ⋅ n ! n
( n − 1)!
3. Звідси виразимо, чому дорівнюватиме n-й член:
10 10
an = an−1 ⋅ , an = ⋅ an−1.
n n
185
100 1
4. Знайдемо початкове значення a 0 : a0 = = = 1, a0 = 1.
0! 1
Процедура. Процедуру, яка буде знаходити елементи послі-
довності, можна побудувати з одного циклу з параметром, у
якому кожного разу при виконанні циклу попереднє значення
буде множитися на 10 і ділитися на значення змінної циклу, як
це обумовлено n.

Мова псевдокоду
Procedure Sequence(Int k);
Int n;
Double a;
begin
a=1;
write('Шукана послідовність');
for n=1 to k do
begin
a=a*10/n;
write(a, ' ')
end;
end;

С++ Python
void Sequence(intk) def Sequence (k):
{ a=1
int n; n=1
double a = 1; print("The desired
printf("The desired sequence")
sequence\n"); for n inrange(1, k + 1):
for (n = 1; n <= k; n++) a = a * 10 / n
{ print ("%.6f" % (a))
a = a * 10 / n;
printf("%.6lf ", a);
}
}

186
Програма
Мова псевдокоду
Program Problem1; {Виведення членів послідовності}
Int k;
Procedure Create_succession(int k);
Int n;
Double a;
begin
a=1;
write(`Шукана послідовність`);
for n=1 to k do
begin
a=a*10/n;
write(a, ' ')
end;
end;
begin
write('Введіть кількість елементів'); readln(k);
Create_succession(k);
end.
С++ Python
void Sequence(intk) { def Sequence (k):
int n; a=1
double a = 1; n=1
printf("The desired print("The desired
sequence\n"); sequence")
for (n = 1; n <= k; n++) { for n inrange(1,k+1):
a = a * 10 / n; a = a * 10 / n
printf("%.6lf\n", print ("%.6f" % (a))
a);
} print("Enter the number of
} members")
int main() k = int(input())
{ Sequence(k)
int n;
printf("Enter the number
of members\n");
scanf("%d", &n);
Sequence(n);
system("pause");
}
187
Приклад 2.2. Складемо програму виведення на екран членів
послідовності десяткових наближень числа 2 .
Правило, за яким будується послідовність, указане словесно.
Треба від словесного задання перейти до рекурентної формули.
Формула для обчислення квадратних коренів. Розглянемо не-
скінченну послідовність x1, x2, x3, ..., утворену за правилом
u +1 1  u 
x1 = , xi = ⋅  xi −1 +  , i = 2, 3, ...
2 2  xi −1 
Виявляється, що члени такої послідовності зі збільшенням номе-
ра i все менше й менше відрізняються від u (цей факт був відо-
мий Герону Олександрійському ще в І ст. н. е.). Нехай, наприклад,
u = 2. Обчислення перших членів послідовності x1, x2, x3, ... дає нам
2 +1
x1 = ≈ 1.5,
2
1  2 
x2 = ⋅  1.5 +  ≈ 1.4166666,
2  1.5 
1  2 
x3 = ⋅  1.4166666 +  ≈ 1.4142156,
2  1.4166666 
1  2 
x4 = ⋅  1.4142156 +  ≈ 1.4142135.
2  1.4142156 
Нагадаємо, що 2 = 1.4142135...
Використовуючи описане рекурентне співвідношення, може-
мо скласти процедуру і програму обчислення квадратних коре-
нів для будь-якого дійсного підкореневого виразу, а не тільки 2.
Процедура
Мова псевдокоду
Procedure Square_root(int n; double u);
int i;
double x;
begin
write('Десяткові наближ. з недол. кв. кореня з ', u);
x=(u+1)/2;
for i=1 to n do
begin
x=(1/2)*(x+u/x);
write(x, ' ')
end;
end;
188
С++ Python
void Square_root(intn, doubleu) def Square_root(n,u) :
{ print("decimal approximation a
int i; square root of the number", u)
double x; x=(u+1)/2;
char forma[1024]; for i inrange(1,n+1):
printf("decimal x=(1/2)*(x+u/x)
approximation a square root of the print("x =","%.{0}f".format(i)
number %lf\n", u); % x)
x = (u + 1.0) / 2;
for (i = 1; i <= n; i++)
{
x = (0.5)*(x + u / x);
sprintf(forma, "x
= %%.%if\n", i);
printf(forma, x);
}
}
Основна програма
Мова псевдокоду
Program Problem2;
int n;
double u;
Procedure Square_root(int n; double u;);
int i;
double x;
begin
write('Десяткові наближ. з недол. кв. кореня з ', u);
x=(u+1)/2;
for i=1 to n do
begin
x=(1/2)*(x+u/x);
write(x, ' ')
end;
end;
begin
write('Введіть підкореневий вираз ');
read(u);
write('Введіть кількість членів послідов. ');
read(n);
Square_root(n, u);
end.
189
С++ Python
void Square_root(intn, doubleu) def Square_root(n,u) :
{ print("decimal approximation a
int i; square root of the number", u)
double x; x=(u+1)/2;
char forma[1024]; for i inrange(1,n+1):
printf("decimal approximation a x=(1/2)*(x+u/x)
square root of the number %lf\n", print("x =","%.{0}f".format(i) %
u); x)
x = (u + 1.0) / 2;
for (i = 1; i <= n; i++) print("Enter the number ")
{ u = float(input())
x = (0.5)*(x + u / x);
sprintf(forma, print("Enter the number of
"x = %%.%if\n", i); members")
printf(forma, x); n = int(input())
} Square_root(n,u)
}
int main()
{
int n;
double u;
printf("Enter the number\n");
scanf("%lf", &u);
printf("Enter the number of
members\n");
scanf("%d", &n);
Square_root(n, u);
system("pause");
return 0;
}

Обчислимо наближене значення квадратного кореня із зада-


ного числа з указаною точністю eps. Тепер нам не треба виводи-
ти на екран усі члени послідовності, а тільки одне значення за-
значеної точності.
Наприклад, поставлено задачу отримати квадратний корінь із
2 з точністю до 0.01. На екран має бути виведене значення цього
кореня (причому зовсім не обов'язково, щоб у його запису було
два знаки після коми, їх може бути більше, але ми будемо знати,
що знаки після сотої частки не є точними).
190
Такі програми можна скласти двома способами.
Спосіб перший полягає в тому, що цикл триватиме до тих
пір, поки різниця між квадратом одержуваного результату і під-
кореневим виразом за абсолютною величиною не стане меншою
або дорівнюватиме зазначеній точності eps. Тоді цикл закінчу-
ється і видається результат.

Перший спосіб. Процедура


Мова псевдокоду
Procedure Square_root1(double eps, double u, double pointer x);
begin
x:=(u+1)/2;
repeat
x:=(1/2)*(x+u/x)
until abs(x*x- u) <= eps
end;
С++ Python
void Square_root1(doubleeps, def Square_root(u, eps, x) :
longu, double *x) x[0] = (u + 1) / 2;
{ while (x[0]*x[0]-u)>eps:
*x = (u + 1.0) / 2; x[0]=(1/2)*(x[0]+u/x[0])
while (((*x)*(*x)-u)>=eps)
{
*x=(1.0/2)*((*x)+u/(*x));
}
}

Програма
Мова псевдокоду
Program Problem2a;
double u;
double x, eps;
Procedure Square_root1(double eps, double u: double pointer x);
begin
x=(u+1)/2;
repeat
x=(1/2)*(x+u/x) ;
until abs(x*x- u) <= eps
end;

191
begin
write('Введіть підкореневий вираз'); read(u);
write('Задайте точність обчислення '); read(eps);
Square_root1(eps, u, x);
write('Квадратний корінь з ', u, ' дорівнює ', x);
write(' з точністю до ', eps)
end.

С++ Python
void Square_root1(doubleeps, def Square_root(u, eps, x) :
doubleu, double *x) x[0] = (u + 1) / 2;
{ while (x[0]*x[0]-u)>eps:
*x = (u + 1.0) / 2; x[0]=(1/2)*(x[0]+u/x[0])
while (((*x)*(*x)-u)>=eps)
{ print("Enter value")
*x=(1.0/2)*((*x)+u/(*x)); u = float(input())
} print("Enter accuracy")
} eps = float(input())
a=[0];
int main() Square_root(u,eps, a)
{ print("the square root of", u, "is =",
double eps; "%.12f" % a[0])
double u;
printf("Enter value\n");
scanf("%lf", &u);
printf("Enter accuracy\n");
scanf("%lf", &eps);
double x;
Square_root1(eps, u, &x);
printf("the square root of %lf
is = %.12lf\n", u, x);
system("pause");
return 0;
}

Другий спосіб полягає в обчисленні двох членів послідовнос-


ті та їхньої різниці за абсолютною величиною та порівнянні із
заданою точністю. Як тільки ця різниця стане рівною або менше
зазначеної точності eps, цикл закінчується.

192
Другий спосіб
Мова псевдокоду
Program Problem2b;
double u;
double x, eps;
Procedure Square_root2(double eps, double u, double pointer x);
Double x1,x2;
begin
x1=1;
x2=(1/2)*(x1+u/x1) ;
repeat
x1=x2;
x2=(1/2)*(x1+u/x1);
until abs(x2- x1) <= eps;
x=x2
end;
begin
write('Введіть підкореневий вираз'); read (u);
write('Задайте точність обчислення '); read(eps);
Square_root2(eps, u , x );
write('Квадратний корінь з ', u, ' дорівнює ', x:12:12);
write(' з точністю до ', eps:3:12)
end.
С++ Python
void Square_root1(doubleeps, def Square_root(u, eps, x) :
doubleu, double *x) x1 = 1.0
{ x2 = (1.0/2)*(x1+u/x1)
double x1, x2; while abs(x2-x1)>=eps :
x1 = 1; x1 = x2
x2 = (1.0 / 2)*(x1 + u / x1); x2 = (1.0/2)*(x1+u/x1)
do { x[0] = x2;
x1 = x2;
x2 = (1.0/2)*(x1+u / x1); print("Enter value")
}while((abs(x2-x1) >= eps)); u = float(input())
*x = x2; print("Enter accuracy")
} eps = float(input())
int main() a=[0];
{ Square_root(u,eps, a)

193
double eps; print("the square root of", u, "is =",
double u; "%.12f" % a[0])
printf("Enter value\n");
scanf("%lf", &u);
printf("Enter the accuracy\n");
scanf("%lf", &eps);
double x;
Square_root1(eps, u, &x);
printf("the square root of %lf
is = %.12lf\n", u, x);
system("pause");
return 0;
}

Проаналізуйте зміст описаних програм і виконайте їх на


комп'ютері. У чому відмінність двох зазначених способів? Який
з них ви вважаєте раціональнішим і чому?
Змініть кожну з цих програм так, щоб на екран виводилися
послідовно десяткові наближення коренів з недостачею; з над-
лишком.
Якщо ви уважно розібралися у складанні та роботі попередніх
програм, то мали змогу помітити, що в програмі з прикладу 2.2,
незважаючи на те, що математично послідовність задана форму-
лою n-го члена, використовується рекурентне співвідношення.
Указується початкове значення a: = 1, а потім у кожному циклі
10
попереднє значення множиться на постійний множник .
n
Отже, при складанні програм найчастіше використовувати-
меться рекурентне співвідношення. Виникає закономірне запи-
тання: як перейти від задання послідовності формулою n-го чле-
на до рекурентного співвідношення? Проаналізуємо цей процес
на частковому прикладі.
Приклад 2.3. Нехай задано послідовність за допомогою фо-
2n − 1
рмули n-го члена, наприклад an = n . Знаходимо за її допо-
2
могою (n-1)-й член:
2(n −1) −1 2n − 3
an−1 = = n−1 .
2n−1 2
194
Розділимо n-й член на (n-1)-й, отримаємо:
2n − 1
an n 2n − 1
= 2 = .
an −1 2 n − 3 4 n − 6
2 n −1
Звідси легко отримати рекурентну формулу:
2n − 1
an = an−1 ⋅ .
4n − 6
Однієї формули недостатньо, необхідно також задати деякі
початкові значення, перші члени послідовності, бажано нульо-
вий член. Матимемо
2 ⋅ 0 − 1 −1 2n − 1
a0 = = = −1 та an = an−1 ⋅ .
20 1 4n − 6
Отже, якщо послідовність задано формулою n-го члена, то
щоб задати її рекурентною формулою, необхідно знайти частку
від ділення n-го члена на (n-1)-й, а потім помножити (n-1)-й
член на частку. Добуток дорівнюватиме n-му члену. Таким чи-
ном ми отримуємо потрібну формулу. До неї необхідно додати
значення деяких перших членів, і послідовність буде задана.
Коротко цей процес можна записати так:
an
= k , an = an −1 ⋅ k , a0 = c.
an −1
Після виконання програми ви, звичайно, звернули увагу, що
незалежно від того, яка точність указана, значення результату
будуть виводитися із 12 знаками після коми (такий формат зада-
но у програмі) і важко буде визначити, скільки десяткових зна-
ків є правильними. Було б дуже зручно, указавши точність,
отримувати в результаті лише необхідну кількість десяткових
знаків, наприклад, указавши 0.001, отримати не більше 3 знаків
після коми. Це можна зробити за допомогою такої функції.
Функція
{Функція визначення порядку k знаків після коми}
Мова псевдокоду
Function t(double eps): integer;
Int k
begin
k = -1;
repeat
195
eps =eps*10;
k =k+1
until eps > 1;
t =k
end;
С++ Python
int t(doubleeps) { def t(eps) :
int k = -1; k=-1
while (eps< 1) whileeps< 1 :
{ eps *= 10
eps *= 10; k += 1
k++; return k;
}
return k;
}

Її робота проста. Уведене значення точності (eps) послідовно


множиться на 10 до тих пір, доки її значення не стане більше 1.
Наприклад, для значення eps = 0.001 процес визначення по-
рядку відбуватиметься так: 0.001 × 10, 0.01 × 10, 0.1 × 10, 1 × 10.
Змінна k підраховує кількість циклів, що виконуються. Однак кі-
лькість циклів буде на 1 більше, ніж кількість знаків після коми.
Чому? Тому, що цикл виконується доти, доки значення eps стане
більше 1. Це зроблено з тих міркувань, що значення eps ніколи точ-
но не дорівнюватиме 1, адже для цієї змінної встановлено дійсний
тип real (double на С++, float мовою Python). Отже, eps не буде дорі-
внювати цілому числу, хоча в прикладі здається, що 0.1×10 = 1, але
це не так. Фактичне значення тут може виражатися числом
0.99999999... . Щоб уникнути незручності, початковому значенню k
надано значення -1, а зупинка циклу виконується після eps >1.
Додаючи цю функцію у програму, легко використовувати її
для виведення результату з необхідною кількістю десяткових
знаків. Тоді дві вищенаведені програми можуть бути побудовані
таким чином:
Мова псевдокоду
Program Problem2a;
double u;
double x, eps;
{Функція визначення порядку:- к-ті k знаків після коми}
196
Function t(double eps): integer;
Int k
begin
k = -1;
repeat
eps =eps*10;
k =k+1
until eps > 1;
t =k
end;
Procedure Square_root(double eps, double u, double poiner x);
begin
x=(u+1)/2;
repeat
x=(1/2)*(x+u/x) ;
until abs(x*x- u) <= eps
end;
begin
write('Введіть підкореневий вираз'); read (u);
write('Задайте точність обчислення'); read (eps);
Square_root(eps, u, x);
write ('Квадратний корінь із ', u, ' дорівнює ', x:6:t(eps));
write ('З точністю до ', eps:1:t(eps))
end.
С++ Python
int t(doubleeps) { def t(eps) :
int k = -1; k=-1
while (eps< 1) { whileeps< 1 :
eps *= 10; eps *= 10
k++; k += 1
} return k+1;
return k+1;
} def Square_root(u, eps, x) :
void Square_root(doubleeps, x[0] = (u + 1) / 2;
doubleu, double *x) { while (x[0]*x[0]-u)>eps:
*x = (u + 1) / 2; x[0]=(1/2)*(x[0]+u/x[0])
while (abs((*x)*(*x)-
u)>eps) print("Enter number")
u = float(input())
*x=(1.0/2)*(*x+u/(*x)); print("Enter accuracy")
} eps = float(input())

197
int main() a=[0];
{ Square_root(u,eps, a)
double eps; print("the square root of", u, "is =",
double u; "%.{0}f".format(t(eps)) % a[0])
printf("Enter value\n"); print("accurate to ",
scanf("%lf", &u); "%.{0}f".format(t(eps)) % eps)
printf("Enter the accuracy\n");
scanf("%lf", &eps);
double x;
Square_root(eps, u, &x);
char forma[1024];
sprintf(forma, "the square root of
%lf is = %%.%if\n", u, t(eps));
printf(forma, x);
sprintf(forma, "accurate to
%%1.%if\n", t(eps));
printf(forma, eps);
return 0;
}

Мова псевдокоду
Program Problem2b;
double u;
double x, eps;
{Функція визначення порядку:- к-ті k знаків після коми}
Function t(double eps): integer;
Int k
begin
k = -1;
repeat
eps =eps*10;
k =k+1
until eps > 1;
t =k
end;
Procedure Square_root(double eps, double u, double pointer x);
var
double x1,x2
begin
198
x1=1;
x2=(1/2)*(x1+u/x1)
repeat
x1=x2;
x2=(1/2)*(x1+u/x1)
until abs(x2- x1) <= eps;
x=x2
end;
{Основна програма}
begin
write('Введіть підкореневий вираз'); read(u);
write('Задайте точність обчислення'); read(eps);
Square_root(eps, u, x);
write('Квадратний корінь із ', u, ' дорівнює ', x:6:t(eps));
write('З точністю до ', eps:1:t(eps))
end.
С++ Python

int t(doubleeps) { def t(eps) :


int k = -1; k=-1
while (eps< 1) { whileeps< 1 :
eps *= 10; eps *= 10
k++; k += 1
} return k+1;
return k + 1;
} def Square_root(u, eps, x) :
void Square_root(doubleeps, x1 = 1.0
doubleu, double *x) x2 = (1.0 / 2) * (x1 + u / x1)
{ while abs(x2 – x1) >= eps :
double x1, x2; x1 = x2
x1 = 1; x2 = (1.0 / 2) * (x1 + u / x1)
x2 = (1.0/2)*(x1+u/x1); x[0] = x2;
do { print("Enter value")
x1 = x2; u = float(input())
x2 = print("Enter the accuracy")
(1.0/2)*(x1+u/x1); eps = float(input())
}while((abs(x2- a=[0];
x1)>=eps)); Square_root(u,eps, a)
*x = x2; print("the square root of", u, "is
} =", "%.12f" % a[0])
print("accurate to ",
int main() "%.{0}f".format(t(eps)) % eps)
{

199
double eps;
double u;
printf("Enter value\n");
scanf("%lf", &u);
printf("Enter the accuracy\n");
scanf("%lf", &eps);
double x;
Square_root(eps, u, &x);
char forma[1024];
sprintf(forma, "the square root of
%lf is = %%.%if\n", u, t(eps));
printf(forma, x);
sprintf(forma, "accurate to
%%1.%if\n", t(eps));
printf(forma, eps);
return 0;
}

Приклад 2.4. Розглянемо нескінченну послідовність x1, x2,...,


побудовану за таким правилом:
u + n −1
x1 = ,
2
1  u 
xi = ⋅  ( n − 1) ⋅ xi −1 + n −1  , (i = 2, 3, ...),
n  xi −1 
де u – невід'ємне дійсне число, n – натуральне число.
Ця послідовність дозволяє отримати скільки завгодно точне
наближення числа n u . За аналогією з програмою для u
n
скласти програму для наближеного обчислення u і перевіри-
3 4
ти її для: а) u ; б) u.

Мова псевдокоду
{Обчислення кореня з будь-яким натур. показ. дійс. числа}
Program Problem3;
double u, n, eps, x;
{Функція визначення порядку k знаків після коми}
Function t(double eps): integer;
var

200
int k;
begin
k=-1;
repeat
eps=eps*10;
k=k+1
until eps > 1;
t=k
end;
Procedure Root(double u, double n, double eps, double pointer x);
double x1, x2, xn;
int i;
begin
x1=(u+n- 1)/2;
xn=1;
for i=1 to trunc(n)- 1 do xn=xn*x1;
x1=((n- 1)*x1+u/xn)/n;
repeat
x1=x2;
xn=1;
for i=1 to trunc(n)- 1 do xn=xn*x1;
x2=((n- 1)*x1+u/xn)/n
until abs(x1- x2) <= eps;
x=x2
end;
begin
write('Введіть підкореневий вираз'); read(u);
write('Введіть показник кореня '); read(n);
write('Введіть точність обчислення'); read(eps);
Root(u, n, eps, x);
write('Значення кореня дорівнює ', x:8:t(eps));
write('З точністю до ', eps:1:t(eps))
end.

С++ Python
int t(doubleeps) { frommathimport trunc
int k = -1; def t(eps) :
while (eps< 1) { k=-1
eps *= 10; whileeps< 1 :
k++; eps *= 10
} k += 1

201
return k + 1; return k+1;
}
def Root(u, eps, n, x) :
void Root(doubleeps, doubleu, xn = 1;
doublen, double *x) x1 = (u + n – 1) / 2;
{ x2 = ((n – 1)*x1 + u / xn) / n;
double x1, x2, xn; while abs(x1 – x2) >eps :
xn = 1; x1 = x2
x1 = (u + n – 1) / 2; xn = 1
x2 = ((n – 1)*x1 + u / xn) / n; for i inrange(1,trunc(n)):
do { xn *= x1
x1 = x2; x2 = ((n-1)*x1+u/xn)/n
xn = 1; x[0] = x2
for (int i=1;i<trunc(n); i++)
xn *= x1; print("Enter the root expression")
x2 = ((n-1)*x1+u / xn) / n; u = float(input())
} while (abs(x1 – x2) >eps); print("Enter the metric of root")
*x = x2; n = float(input())
} print("Enter accuracy")
eps = float(input())
int main() a=[0];
{ Root(u, eps, n, a)
double eps; print("the", n, "root of", u, "is =",
double u; "%.{0}f".format(t(eps)) % a[0])
double n; print("accurate to ",
printf("Enter the root expression\n"); "%.{0}f".format(t(eps)) % eps)
scanf("%lf", &u);
printf("Enter the metric of root\n");
scanf("%lf", &n);
printf("Enter the accuracy\n");
scanf("%lf", &eps);
double x;
Root(eps, u, n, &x);
char forma[1024];
sprintf(forma, "the %lf root of
%lf is = %%.%if\n", n, u, t(eps));
printf(forma, x);
sprintf(forma, "accurate to
%%1.%if\n", t(eps));
printf(forma, eps);
return 0;
}

202
Приклад 2.5. Послідовність задана формулою
( − 1) n −1 ⋅ x 2 n −1
un = , | x |≤ 1.
(2 n − 1)!
Вивести на екран члени послідовності до члена, меншого від
заданого додатного числа eps. Прийняти 0 < eps < 1.
Найважливіше – це перейти від формули n-го члена до реку-
рентного співвідношення, яке потім треба використовувати при
складанні програми.
Запишемо формулу (n-1)-го члена:
( −1) n − 2 ⋅ x 2 n − 3
u n −1 = .
(2 n − 3)!
Розділимо un на un-1, отримаємо:
un (−1)n−1 ⋅ x2n−1 ⋅ (2n − 3)!
= =
un−1 (−1)n−2 ⋅ x2n−3 ⋅ (2n − 1)!
(−1) ⋅ x2 ⋅ (2n − 3)! − x2
= = .
(2n − 3)!⋅ (2n − 2) ⋅ (2n − 1) (2n − 2)(2n − 1)
Звідси
x2
un = −u n −1 .
(2 n − 2)(2 n − 1)
Зазначимо, що перший член дорівнює u1 = x.
Після цього не важко скласти програму. Організуємо цикл із
передумовою "доки": доки |u| > eps. Чому для цієї задачі зручно
будувати саме такий цикл?
У нашому прикладі за початкове значення беремо перший
елемент послідовності. Може статися, що користувач задав
таке значення eps, що вже перший член менше нього. Тоді
обчислення робити не варто, тобто цикл не виконується,
отже, необхідно одразу перевірити умову. Таке роблять у ци-
клі з передумовою. Цикл repeat ... until (у Python такого
циклу немає, у С++ аналогом є do … while зі зворотною умо-
вою) буде обов'язково виконуватись хоча б один раз, а нам
цього не треба.

203
Програма
Мова псевдокоду
Program Problem4;
var
double x, eps;
{Функція визначення порядку: k знаків після коми}
Function t(double eps): integer;
Int k;
begin
k=-1;
repeat
eps=eps*10;
k=k+1
until eps > 1;
t=k
end;
Procedure Create_succession(double x, double eps);
Double u;
Int n;
begin
u=x;
n=1;
while abs(u) > eps do
begin
n=n+1;
writeln(u:6:t(eps), '; ');
u=(-1)*u*x*x/((2*n- 2)*(2*n- 1))
end;
end;
begin
write('Введіть значення |x| <= 1 '); readln(x);
write('Задайте додат. число eps '); readln(eps);
writeln('Члени послідовності');
Create_succession(x, eps);
end.
С++ Python
int t(doubleeps) { def t(eps) :
int k = -1; k=0
while (eps< 1) { whileeps< 1 :
eps *= 10; eps *= 10
k++; k += 1
204
} return k;
return k + 1;
} def Create_succession(x, eps) :
void Create_succession(doublex, u=x;
doubleeps) n=1;
{ while abs(u)>eps :
double u = x; n=n+1
int n = 1; print("%.{0}f".format(t(eps))
char forma[1024]; % u)
while (abs(u) >eps) u = -1 * u* x*x / ((2 * n – 2)*(2 * n
{ – 1))
n = n + 1; print("Enter the |X|<=1")
sprintf(forma, "%%6.%if\n", x = float(input())
t(eps)); print("Enter the EPS")
printf(forma, u); eps = float(input())
u=-1*u*x*x/((2*n-2)*(2*n-1)); print("members of the sequence")
} Create_succession(x, eps)
}
int main()
{
double eps;
double u;
printf("Enter the |X|<=1\n");
scanf("%lf", &u);
printf("Enter the EPS\n");
scanf("%lf", &eps);
printf("members of the
sequence\n");
Create_succession(u, eps);
system("pause");
return 0;
}

Приклад 2.6. Нехай послідовність задано формулою


m(m −1)...(m − n + 1) n
un = ⋅x ,
n!
де m – дійсне число, що відрізняється від 0 та всіх натуральних
чисел, | x |<1.
Вивести на екран члени послідовності до члена, меншого від
наперед заданого додатного числа eps, 0 < eps < 1.
205
З формули n-го члена складіть рекурентну формулу, устано-
віть, яким має бути початкове значення, складіть програму й
порівняйте її з наведеною нижче.
Мова псевдокоду
Program Problem5;
double x, m, eps;
{Функція визначення порядку: k знаків після коми}
Function t(double eps): integer;
Int k;
begin
k=-1;
repeat
eps=eps*10;
k=k+1
until eps > 1;
t=k
end;
Procedure Create_succession(double x, double m, double eps);
Int n;
double u;
begin
u=1;
n=1;
repeat
u=u*(m- n+1)*x/n;
write(u:3:t(eps), ' ');
n=n+1
until abs(u) <= eps;
writeln
end;
{ Основна програма }
begin
write('Введіть значення x, |x| < 1 '); readln(x);
writeln('Введіть дійсне додатне m, що відрізняється від');
write('натуральних чисел '); readln(m);
write('Задайте додатне число eps '); readln(eps);
writeln('Члени послідовності');
Create_succession(x, m, eps)
end.

206
С++ Python
int t(doubleeps) { def t(eps) :
int k = 0; k=0
while (eps< 1) { whileeps< 1 :
eps *= 10; eps *= 10
k++; k += 1
} return k;
return k;
} def Create_succession(x, m, eps) :
void Create_succession(doublex, u=1;
doublem, doubleeps) n=1;
{ while abs(u)>eps :
double u = 1; u = u * (m – n + 1)*x / n
int n = 1; print("%.{0}f".format(t(eps))
char forma[1024]; % u)
while (abs(u) >eps) n += 1
{
u = u * (m – n + 1)*x / n;
print("Enter the |X|<=1")
sprintf(forma,"%%3.%if\n",t(eps)); x = float(input())
printf(forma, u); print("Enter the 0!=m!=N")
n = n + 1; m = float(input())
} print("Enter the 0<EPS<1")
} eps = float(input())
int main() print("members of the sequence")
{ Create_succession(x, m, eps)
double x;
double m;
double eps;
printf("Enter the |X|<=1\n");
scanf("%lf", &x);
printf("Enter the 0!=m!=N\n");
scanf("%lf", &m);
printf("Enter the 0<EPS<1\n");
scanf("%lf", &eps);
printf("members of the
sequence\n");
Create_succession(x, m, eps);
system("pause");
return 0;
}

207
Ускладнимо задачу та проаналізуємо такий приклад.
Приклад 2.7. Дано додатне число eps. Послідовність a1, a2,
a3,... побудована за таким правилом:
 1  1  1 
ai =  1 −   1 −  ⋅ ... ⋅  1 − .
 2  3   i + 1
Знайти перший член an послідовності, для якого виконано умову
(an – an-1) < eps. Скласти програму виконання цього завдання.
Алгоритм
1. Початок.
Змінні та їхні типи. Змінна i набуває цілих значень:
1, 2, 3, 4, ... Змінні ap, an та eps набувають дійсних значень.
2. Основна частина.
а) Уведення значень eps.
б) Установлення початкового значення змінних i, ap та an:
i:=1, ap:=1, an:=1- 1/2.
в) Цикл "доки" з умовою: abs(an- ap) >= eps.
Команди у циклі. Значення i у циклі кожного разу має збіль-
шуватись на одиницю; значення ap:=ap*(1- 1/i); значення
an:=ap*(1- 1/(i+1)).
г) Після завершення роботи циклу – виведення значення an
на екран. Можна вивести й номер i цього члена послідовності.
3. Кінець.
Складемо процедуру та програму, використовуючи алгоритм.
Програма
Мова псевдокоду
Program Problem6;
int i;
double a, eps;
{Функція визначення порядку: k знаків після коми}
Function t(double eps): integer;
Int k;
begin
k=-1;
repeat
eps=eps*10;
k=k+1
208
until eps > 1;
t=k
end;
Procedure Create_succession(double eps; int var i, double var a);
Double an, ap;
begin
i=1; ap=1; an=1- 1/2;
while abs(an- ap) >= eps do
begin
i=i+1;
ap=an;
an=ap*(1- 1/(i+1))
end;
a=an
end;
{ Основна програма }
begin
write('Введіть будь-яке додатне число eps '); readln(eps);
Create_succession(eps, i, a);
writeln('Шуканий член послідовності ', a:6:t(eps));
writeln('Міститься на ', i, '-ому місці в послідовності')
end.
С++ Python

int t(doubleeps) { def t(eps) :


int k = 0; k=0
while (eps< 1) { whileeps< 1 :
eps *= 10; eps *= 10
k++; k += 1
} return k;
return k;
} def Create_succession(eps, i, a) :
void Create_succession(doubleeps, i[0] = 1;
int *i, double *a) ap = 1
{ an = 1 – 1 / 2
*i = 1; while abs(an-ap) >= eps :
double ap = 1; i[0] = i[0] + 1;
double an = 1 – 1.0 / 2; ap = an;
while (abs(an-ap) >= eps) an = ap * (1 – 1 / (i[0] + 1))
{ a[0] = an;

209
*i = *i + 1; print("Enter the 0<EPS")
ap = an; eps = float(input())
an = ap*(1- i = [0];
1.0/(*i+1)); a = [0.0];
} print("members of the sequence")
*a = an; Create_succession(eps, i , a)
} print("value =",
int main() "%.{0}f".format(t(eps)) % a[0])
{ print("in position", i[0])
double eps;
printf("Enter the
0<EPS\n");
scanf("%lf", &eps);
int i;
double a;
Create_succession(eps,
&i, &a);
char forma[1024];
sprintf(forma, "value =
%%3.%if\n", t(eps));
printf(forma, a);
printf("in %d position\n",
i);
system("pause");
return 0;
}

Визначення границі послідовності. Число A називається


границею послідовності xn, якщо для будь-якого додатного чис-
ла ε знайдеться такий номер послідовності N, що для всіх но-
мерів n > N буде виконуватися нерівність ( xn − A) < ε.
Остання нерівність рівносильна такій:
−ε < xn − A < ε.
Якщо зобразити числа A, A − ε, A + ε та значення послідов-
ності xn на числовій осі, то отримаємо наочне геометричне роз'-
яснення границі послідовності (рис. 2.1):

210
Рис. 2.1

Який би малий відрізок (довжиною 2 ⋅ ε ) із центром у точці


A (див. рис. 2.1) не взяли, усі точки xn, починаючи з деякої з них,
мають потрапити всередину цього відрізка (тож поза його гра-
ницями може залишатися лише кінцева кількість точок). Точка,
що зображує границю A, є центром згустку точок, що зображу-
ють значення членів послідовності.
Приклад 2.8. Програма, яка показує, що прикладом послідо-
(−1)n
вності yn = 2 + є число 2.
n

Програма
Мова псевдокоду
Program Problem7;
longint i, n, k;
double eps, yn, yp;
{Обчислення порядку – кількості знаків після коми}
Function t(double eps): integer;
int k;
begin
k=-1;
repeat
eps=eps*10;
k=k+1
until eps > 1;
t=k
end;
{ Основна програма }
begin

211
writeln('Введіть довільне додатне число, ');
write('можна навіть дуже мале '); readln(eps);
i=1; k=-1; yn=1;
while abs(yn-2) >= eps do
begin
i=i+1;
k=k*(-1);
yn=2+k/i
end;
writeln(Умову abs(yn-2)<', eps:1:t(eps), ' задовольняє);
writeln('член послідовності yn = ', yn:6:t(eps), ',');
writeln('міститься під номером ', i);
write('Введіть номер члена послідовності більше ',i,' ');
readln(n);
if n mod 2 = 0 then yn:=2+1/n else yn:=2- 1/n;
if abs(2- yn) < eps hen
begin
write(Нерівність abs(2- ', yn:6:t(eps), ') < ', eps:1:t(eps));
writeln( ' виконується ')
end else
begin
write(Нерівність abs(2- ', yn:6:t(eps), ') < ', eps:1:t(eps));
writeln(' не виконується')
end
end.

С++ Python
int t(doubleeps) { def t(eps) :
int k = 0; k=0
while (eps< 1) { whileeps< 1 :
eps *= 10; eps *= 10
k++; k += 1
} return k; return k;
}
int main() yn = 1
{ k = -1
double eps, yn = 1, k = -1; i=1
long i = 1, y = 1; y=1
printf("Enter the 0<EPS\n"); print("Enter the 0<EPS")
scanf("%lf", &eps); eps = float(input())

212
while (abs(yn – 2) >= eps) while abs(yn – 2) >= eps :
{ i = i + 1;
i = i + 1; k = k * -1;
k = k * -1; yn = 2 + k / i;
yn = 2 + k / i; print("abs(yn-2) <",
} "%1.{0}f".format(t(eps)) % eps)
char forma[1024]; print("yn =",
sprintf(forma, "abs(yn-2) < "%6.{0}f".format(t(eps)) % yn)
%%1.%if\n", t(eps)); print("in position", i)
printf(forma, eps); print("Enter n>position\n");
sprintf(forma,"yn = n = int(input())
%%6.%if\n", t(eps)); if n%2 == 0 :
printf(forma, yn); yn = 2 + 1.0 / n
printf("in %d position\n", i); else :
printf("Enter n>position\n"); yn = 2 – 1.0 / n;
long n; print("Inequality abs(2 -%6.{0}f <
scanf("%d", &n); ".format(t(eps)) %
if (n % 2 == 0) yn,"%1.{0}f)".format(t(eps)) % eps);
yn = 2 + 1.0 / n; if abs(2 – yn) < eps :
else print("TRUE!\n")
yn = 2 – 1.0 / n; else:
sprintf(forma, "Inequality print("FALSE!\n")
abs(2-%%6.%if)<%%1.%if\n",
t(eps), t(eps));
printf(forma, yn, eps);
if (abs(2 – yn) < eps)
printf("TRUE!\n");
else
printf("FALSE!\n");
system("pause");
return 0;
}

Продовжимо складання програм на послідовності, трохи


ускладнивши завдання. Якщо у двох попередніх прикладах було
відоме число, яке є межею послідовності, то в наступному при-
кладі треба буде визначити, чи існує взагалі межа, а якщо існує,
то знайти її та показати, що вона дійсно є межею заданої послі-
довності. Таким чином, завдання має дві частини:
1) установити існування межі;
2) якщо така існує, то показати, чому вона дорівнює.

213
Перед виконанням завдання звернемося до математичного
аналізу, у якому є теорема, що належить чеському математику
Больцано і французькому математику Коші, її часто називають
принципом збіжності. Вона висловлює загальну ознаку існуван-
ня кінцевої межі послідовності.
Нехай задана послідовність xn, що пробігає значення x1, x2, ..., xn,
..., xm, ... Для того щоб послідовність xn мала кінцеву межу, необ-
хідно й достатньо, щоб для будь-якого числа ε > 0 існував такий
номер N, щоб нерівність ( xn − xm ) < ε виконувалася для всіх
n > N та m > N . Суть полягає в тому, щоб значення змінних xn
та xm між собою безмежно зближалися при зростанні їхніх номерів.
Ця ознака допускає просту геометричну ілюстрацію (рис. 2.2).

Рис. 2.2

Приклад 2.9. Виявити, чи має межу послідовність, задана


n
 1
формулою xn = 1 +  . Якщо має, то знайти її.
 n
Алгоритм розв'язання ґрунтується на ознаці існування межі
послідовності. Нам необхідно встановити, що члени послідов-
ності зі збільшенням номера зближуються один з одним. Пере-
кладаючи це мовою математики, треба задати додатне число eps,
яке може бути як завгодно малим, і знайти такі два члени послі-
довності, різниця між якими за абсолютною величиною буде
менше цього, наперед заданого, навіть дуже малого числа eps.
Ідея стає зрозумілою. Залишається продумати, як знаходити
члени послідовності залежно від їхніх номерів.
Звичайно, можна скласти рекурентне співвідношення, але
це буде трохи важко із суто математичного погляду. Інша дум-
ка – скласти підпрограму піднесення до степеня та із основної
програми кожного разу звертатися до неї для обчислення сте-

214
пеня зі зміною номера n. Досвід складання підпрограм у нас
уже є: на минулих заняттях ми складали багато підпрограм у
формі процедур і функцій.
Основна програма. Спочатку попросимо користувача задати
точність обчислення (так ми назвали число eps).
Потім задається початкове значення змінної n (n: = 1). Орга-
нізовується цикл з умовою (repeat ... until ...), у якому змінній x –
члену послідовності – присвоюється значення, що обчислює
значення функції x = s ( n, (1 + 1 / n)) для даного значення n. Далі
значення n збільшується на 1 ( n := n + 1) і обчислюється на-
ступний член послідовності x1, який також буде дорівнювати
значенню функції x1 = s ( n , (1 + 1 / n )) (пам'ятайте, що зна-
чення n уже збільшилося на 1).
Перевіряємо умову abs (x1-x) < eps. Як тільки умова викона-
ється, цикл закінчиться. Як значення межі послідовності ми мо-
жемо брати будь-яке зі значень x або x1, воно вкаже значення
межі послідовності із заданою точністю eps.
Якщо хто-небудь із вас раніше зустрічався з такою послідов-
ністю, то він, скоріш за все, був упевнений, що її межа існує й
дорівнює числу e – основі натурального логарифма.

Процедура
Мова псевдокоду
Function s(int k; double p): double;
Int i;
double z;
begin
z=1;
for i=1 to k do z=z*p;
s=z
end;
С++ Python
double s(intk, doublep) { def s(k, p) :
double z = 1; z = 1;
for (int i = 1; i <= k; i++) for i inrange(1,k+1) :
z *= p; z *= p;
return z; return z;
}

215
Програма
Мова псевдокоду
Program Problem8;
int n;
double x, x1, eps;
{ Функція обчислення порядку k значень після коми}
Function t(double eps): int;
int k;
begin
k=-1;
repeat
eps=eps*10;
k=k+1
until eps > 1;
t=k
end;
Function s(int k; double p): double;
int i;
double z;
begin
z=1;
for i=1 to k do z=z*p;
s=z
end;
begin
write('Задайте точність '); readln(eps);
n=1;
repeat
x=s(n, (1+1/n));
n=n+1;
x1=s(n, (1+1/n))
until abs(x1- x) < eps;
writeln('Межа послідовності дорівнює ', x1:6:t(eps));
writeln('З точністю до ', eps:1:t(eps))
end.
С++ Python
int t(doubleeps) { def t(eps) :
int k = 0; k=0
while (eps< 1) { while eps< 1 :
eps *= 10; eps *= 10
k++; k += 1
216
} return k;
return k;
} def s(k, p) :
double s(intk, doublep) { z = 1;
double z = 1; for i inrange(1,k+1) :
for (int i = 1; i <= k; i++) z *= p;
z *= p; return z;
return z; n = 1;
} print("Enter the 0<EPS");
int main() eps = float(input())
{ while True:
double eps, x, x1; x = s(n, (1 + 1 / n))
int n = 1; n=n+1
printf("Enter the 0<EPS\n"); x1 = s(n, (1 + 1 / n))
scanf("%lf", &eps); if abs(x1-x) < eps :
do { break;
x = s(n, (1 + 1.0 / n));
n = n + 1; print("limit of sequense is
x1 = s(n, (1 + 1.0 %6.{0}f".format(t(eps)) % x1)
/ n)); print("accurate to
} while (abs(x1 – x) > eps); %1.{0}f".format(t(eps)) % eps)
char forma[1024];
sprintf(forma, "limit of
sequense is %%6.%if\n", t(eps));
printf(forma, x1);
sprintf(forma, "accurate to
%%1.%if\n", t(eps));
printf(forma, eps);
system("pause");
return 0;
}

Вправи
1. Дано дійсне число b > 0. Послідовність a1, a2, ... побудо-
вана за таким правилом: a1 = 1, ai = ai2−1 + 1 (i = 2, 3, ...). Треба
отримати всі a1, a2, ..., які менше чи дорівнюють b.
2. Дано дійсне число b > 0. Послідовність a1, a2,... побудова-
1
на за правилом: a1 = b, ai = ai −1 − , i = 2, 3, ... . Знайти пе-
i
рший від'ємний член послідовності a1, a2, ... .

217
3. Скласти програму обчислення й виведення на екран n чле-
нів послідовності, заданої формулою n-го члена. Попередньо
скласти рекурентну формулу.
(n + 2)! 2n −1 n2 2n − 1
а) an = n
; б) an = n +1
; в) a n = n
; г) an = n ;
3 3 3 3 ⋅ n!
n n2 n xn
д) an = 3 n ; е) an = ; є) an = 2 4 ; ж) an = ,
n⋅2 n! n n!
де x – задане дійсне число, x > 1;
3
з) an = ( −1) n +1 n n .
2
4. Скласти програму, що підраховує суму n перших членів
послідовності, заданої формулою n-го члена. Попередньо склас-
ти рекурентну формулу.
xn n
а) an = 10n ⋅ xn ; б) an = (−1)n +1 ⋅ ; в) an = x n −1 ;
n n ⋅10
г) an = n!⋅ xn ; д) an = 2n−1 ⋅ x2(n−1) ;
x 2 n −1 n−1 n−1
е) an = ( −1) n +1 ; є) an = (n − 1) ⋅ 3 ⋅ x ;
(2 n − 1) ⋅ (2 n − 1)!
xn ( nx ) n ln(n + 1) n+1
ж) an = ; з) an = ; и) an = ⋅x ,
n ( n + 1) n! n +1
де x – задане дійсне число.
5. Дано дійсне b < 0. Послідовність a1 , a2, ... побудована за
a i −1 + 1
таким правилом: a1 = b, ai = , i = 2, 3, ... .
i ⋅ sin 2 i
Знайти перший невід'ємний член послідовності.
6. Фішка може рухатися по полю довжиною N тільки вперед.
Довжина ходу фішки не більше K. Знайти кількість різних шля-
хів, якими фішка може пройти поле від початку до кінця.
Приклад. N=3, K=2. Можливі шляхи:
1,1,1
1,2
2,1

218
7. Покупець має купюри номіналом A(1), ..., A(n), а продавець
– B(1), ... , B(m). Необхідно знайти максимальну вартість товару
Р, яку покупець не зможе оплатити, оскільки не матиме змоги
точно розрахуватися із продавцем, хоча грошей на покупку то-
вару в нього достатньо.
8. Покупець має n монет номіналом H(1), ... , H(n). Продавець
має m монет номіналом B(1), ... , B(l). Чи зможе покупець купити
річ вартістю S так, щоб у продавця знайшлась точна решта (як-
що вона необхідна).
9. Дано натуральне n. Обчислити:
 1  1   1 
 1 + 2   1 + 2  ⋅ ... ⋅  1 + 2  .
 1  2   n 
10. Дано опуклий n-кутник, n=>3. Розбити його на трикутни-
ки (n-3) діагоналями, що перетинаються лише на кінцях, таким
чином, щоб:
а) cума їхніх довжин була мінімальною;
б) максимальна з діагоналей мала найменшу довжину.
11. Для чисел Фібоначчі u0, u1, ... справедлива формула Біне:
k k
 1  1 + 5   1  1 − 5 
uk =   ⋅   −   ⋅  , где k = 0, 1, ... .
 5  2   5  2 

1− 5
Оскільки < 1, то для великих k виконано наближену
2
k
 1   1+ 5 
рівність uk =  ⋅  . Обчислити й округлити до най-
 5   2 
ближчого цілого усі числа
k
 1   1+ 5 
 ⋅  (k = 0, 1, ..., 15),
 5   2 
а також обчислити u0, u1, ..., u15 за формулами та порівняти
u 0 = 0; u1 = 1; u k = u k −1 + u k − 2 ( k = 2, 3, ...), потім порів-
няти результати.

219
12. З послідовності, що складається із N чисел, викреслити
мінімальну кількість елементів так, щоб ті, що залишаться,
створили строго зростаючу послідовність.
13. Обчислити й вивести на екран додатні значення функції
n
y = sin( nx ) − cos   • р Џ n = 1, 2, ..., 50.
x
14. Нехай x = a1, a2,..., am) та y = (b1, b2,..., bn) – два задані
рядки символів. Визначимо d(x, y) як мінімальну кількість вста-
вок, видалень і замін символу, яка необхідна для перетворення
їx на y. Наприклад: d(ptslddf, tsgldds) = 3.

видалення p вставлення g заміна f


pstlddf stlddf stglddf stgldds

Для заданих x та y знайти d(x, y).


xk
15. Обчислити значення функції z = , яка більше заданого
k2
числа a, якщо k = 1, 2, 3, ... .
x2 x3 xn
16. Обчислити члени ряду , − , ..., (−1)n ⋅ , ...,
2! 3! n!
модуль яких більше заданого числа a, 0 < a < 1, x – будь-яке
дійсне число.
17. У заданій послідовності цілих чисел знайти максимально
довгу підпослідовність таку, щоб кожен наступний елемент під-
послідовності ділився націло на попередній.
18. У коло радіусом r вписано багатокутник зі стороною an.
Сторона багатокутника з подвоєною кількістю сторін визнача-
ється за формулою a2 n = 2r 2 − 2r r 2 − an2 / 4 . Знайти a128, як-
що відомі r та а4.
19. Обчислити й вивести на екран значення членів ряду
x + h x + 2h x + 3h x + nh x + 20h
, , , ..., , ..., .
3 5 7 2n + 1 41
220
15
 n+i 
20. Обчислити значення функції z = ∏  .
i =1  i 
21. Задано z та y – дві послідовності. Чи можна отримати по-
слідовність z викресленням елементів з y? Послідовність an на-
буває значень
a1 = 0, 9; a2 = 0, 99; a3 = 0,999; ..., an = 0,999...9; ... .
22. Чому дорівнює границя an при n → ∞ ? Яким має бути n
для того, щоб абсолютна величина різності між an та її грани-
цею була більше 0,0001?
23. Установити, чи має наведена послідовність un межу:
1 1 1
u1 = 1, u2 = , u3 = , ..., un = 2 , ... .
4 9 n
2
24. Показати, що послідовність xn = 4 n 2 + 1 при необмеже-
3n + 1
4
ному зростанні n прямує до межі, що дорівнює .
3
Установити, чи має послідовність, задана формулою n-го
члена, межу:
cn c
а) xn = (c > 0), xn+1 = xn ⋅ ;
n! n +1
б) x1 = c, x2 = c + c , ..., xn = c + c + ... + c .
Тут xn+1 отримуємо з xn за формулою xn+1 = c + xn ;
c c x2
в) x1 = , xn +1 = + n , • р Џ 0 < c ≤ 1.
2 2 2

2.2. Рекурсія
Поговорити про рекурсію детальніше.
Рекурсія – це спосіб організації підпрограми, при якому в
процесі виконання вона звертається сама до себе.
Перш за все, треба дати рекурсивне визначення постановці
завдання. Воно складається з умови закінчення рекурсії та влас-
не тіла рекурсії.
221
Приклад 2.10. Розглянемо задачу обчислення суми чисел.
При стандартному підході ми рахували суму таким чином: до
деякої змінної (початкове значення якої дорівнює нулю) додавали
значення першого числа, до результату – значення другого; про-
цес продовжували доти, поки не вичерпували всю кількість чисел.
Отже, визначення процесу підсумовування може бути таке:
до поточного значення суми додати наступне число зі списку,
поки не вичерпається список.
У межах рекурсивного підходу ми по-іншому будуємо обчи-
слювальний процес. Припустимо, що нам відома сума попере-
дніх чисел. Тоді для того щоб знайти суму чисел, треба до відо-
мої суми додати останнє число, що залишилося. Звідси маємо
таке рекурсивне визначення процесу підсумовування:
• Якщо число одне, то сума дорівнює цьому числу.
• В іншому випадку сума дорівнює: останнє число зі списку
додати до суми попередніх чисел.
Зверніть увагу, що в другому пункті можна до останнього
числа додавати суму чисел, що залишилися, а до першого – зі
списку підсумовування.
Сформулюємо визначення формальніше. Нехай n – кількість
чисел для підсумовування, Sum (n) – ім'я процесу підсумовуван-
ня n чисел, x – поточне число із заданої множини для підсумо-
вування. Тоді визначення буде таким:
Якщо n = 1 то Ввести число x і Sum: = x.
Інакше Ввести число x і Sum: = x + Sum (n-1)
Слід одразу сказати, що коли написане рекурсивне визначен-
ня, написання програми мовою Python не становитиме жодних
труднощів: просто кожен пункт визначення замінимо відповід-
ним оператором і додамо опис змінних.
Псевдокод
program demo_rek1;
function sum(n:byte):byte;
var x:byte;
begin
if n=1 then
begin
read(x);
sum:=x
end else
222
begin
read(x);
sum:=x+sum(n-1);
end;
end;
begin
read(n);
write('sum=',sum(n));
end.
С++
int sum(intn)
{
int x;
if (n == 1)
{
scanf("%d",&x);
return x;
}
else
{
scanf("%d", &x);
return x + sum(n – 1);
}
}
int main()
{
int n;
scanf("%d", &n);
printf("sum=%d\n", sum(n));
return 0;
}
Python
def sum(n):
if (n == 1):
x = int(input())
return x
else :
x = int(input())
return x + sum(n – 1)
a = int(input())
print("sum =", sum(a))

223
Якщо з клавіатури вводиться п'ять чисел: 4 5 4 3 2, то про-
грама надрукує результат 14. Перевірте.
Розглянемо ще один приклад – друкування перших натура-
льних чисел для демонстрації прямого та зворотного ходу реку-
рсії. Нехай програма має вигляд:
Псевдокод
program demo_rek2;
procedure druk(n:byte);
begin
if n=1 then write(n)
else
begin
write(n);
druk(n-1);
write(n);
end;
end;
begin
druk(10);
end.
С++
void druk(intn)
{
if (n == 1)
printf("%d", n);
else
{
printf("%d", n);
druk(n – 1);
printf("%d", n);
}
}
int main()
{
druk(10);
printf("\n");
return 0;
}
Python
def druk(n):
global s

224
if (n == 1):
s+=str(n)
else :
s+=str(n)
druk(n – 1)
s+=str(n)
s=""
druk(10)
print(s)

У результаті її роботи буде надрукований такий рядок цифр:


109876543212345678910
Розберемо докладніше механізм роботи рекурсії. У програмі
викликається процедура druk із фактичним параметром значен-
ням, що дорівнює 10. Керування передається підпрограмі druk.
При цьому під параметр процедури виділяється пам'ять і в неї
копіюється значення фактичного параметра (назвемо його внут-
рішньою змінною рекурсії).
Далі, оскільки внутрішня змінна рекурсії дорівнює одиниці,
то переходимо на гілку умовного оператора else, де виконується
виклик процедури друкування числа і наступний виклик проце-
дури druk, але вже з фактичним параметром на одиницю менше
і т. д. Кожного разу при виклику процедури druk під параметр
буде виділятися пам'ять, у яку буде копіюватися нове значення
фактичного параметра. Процес триватиме, поки не спрацює
умова зупинки рекурсивних викликів, тобто n = 1. Цю частину
процесу назвемо прямим ходом рекурсії.
Після останнього виклику процедури druk, тобто коли n = 1,
відбудеться друкування значення 1 і процедура завершить своє
виконання (точніше, завершить виконання процедура, викликана
останньою). При цьому знищується внутрішня змінна, відповідна
до цього виклику, і керування передається наступному оператору
за точкою виклику процедури. Таким оператором є оператор ви-
клику процедури друкування значення внутрішньої змінної пото-
чного виклику процедури. Після друку процедура знову завершує
роботу, керування передається оператору наступної за точкою
виклику процедури і т. д. Цю частину процесу назвемо зворотним
ходом рекурсії. Потім процес повторюється аналогічно.

225
Зобразимо цей процес схематично. Позначимо через внутрішню
змінну рекурсії nі виклик процедури druk. Отримаємо (рис. 2.3):

Рис. 2.3

Стрілки показують виклики процедури druk (стрілка право-


руч – униз) і повернення при закінченні роботи druk (ліворуч).
Перед тим як писати рекурсивні процедури та функції, слід
дати рекурсивне визначення постановці завдання.
Більшість математичних функцій і алгоритмів, будучи запро-
грамовані в Python, найприродніше виражаються саме в рекур-
сивній формі. У більшості випадків рекурсивне розв'язання за-
дачі виходить досить простим, але водночас украй неефектив-
ним порівняно з ітеративною реалізацією алгоритму.
Правильно організований рекурсивний процес розвивається
таким чином, що будь-яка складна ситуація врешті-решт зво-
диться до умови припинення рекурсії.
Наведемо приклади, які вже стали класичними, використання
рекурсій у підпрограмах.
Приклад 2.11. Для заданого числа n обчислити n перших чи-
сел Фібоначчі, якщо при n = 1, F = 1 та n = 2 F = 1, а кожне на-
ступне число є сумою двох попередніх. Запишемо формулу для
їх обчислення:
Fn = Fn −1 + Fn − 2 , F1 = 1, F2 = 1.
Дамо рекурсивне визначення послідовності Фібоначчі. По-
значимо через F (n) значення n-го числа Фібоначчі. Матимемо:
Якщо n = 1 або n = 2 то F = 1
Інакше F: = F (n-1) + F (n-2).
Власне, ми навіть написали функцію.
226
Псевдокод
function F(n:byte):integer;
begin
if (n=1) or (n=2)
then F:=1
else F:=F(n-1)+F(n-2)
end;
begin
writeln('Fib=', F(n));
end.
С++
int F(intn)
{
if ((n == 1) || (n == 2))
return 1;
else
return F(n – 1) + F(n – 2);
}
printf("Fib=%d", F(n));
Python
def F(n):
if (n==1) | (n==2):
return 1
else :
return F(n-1)+F(n-2)
print("Fib=",F(n))

Однак, незважаючи на зовнішню вишуканість, ця функція


вкрай неефективна. Давайте детально простежимо за ходом її
роботи.
Коли n = 1 або n = 2, отримуємо значення функції, виконав-
ши її один (перший) раз.
Коли n = 3, виконується друга (else) гілка умовного операто-
ра, а значення функції знаходимо з виразу fib (2) + fib (1).
Для того щоб обчислити значення виразу, слід ще два рази
(рекурсивно) звернутися до функції fib.
Коли n = 4, функція буде виконуватися п'ять разів, а коли
n = 5 – дев'ять разів (рис. 2.4).

227
Рис. 2.4

При зростанні значення параметра функції дуже швидко зро-


стає кількість звернень до неї, а отже, збільшується час обчис-
лення. Це відбувається через те, що друга рекурсивна гілка умо-
вного оператора містить одразу два рекурсивні виклики. Таким
чином, ця рекурсивна функція є прикладом неефективних реку-
рсивних функцій. Однак слід зазначити, що ітераційна реаліза-
ція обчислення чисел Фібоначчі, хоч і виконується швидше, але
вимагає використання додаткових змінних.
Приклад 2.12. Даний приклад показує принципову відмін-
ність між ітерацією й рекурсією для друкування цілих чисел від
1 до i з кроком 1. Ітерації потрібні цикл і локальна змінна k як
параметр циклу. Рекурсії нічого цього не треба.

Псевдокод
program iterat_reсurs;
var n:integer;
procedure reсursion (i:integer);
begin
writeln(i);
if i > 1 then reсursion(i-1);
end; (* Рекурсія *)
procedure iterat(i:integer);
var k,m:integer;
begin
k:=1; m:=i;
while m >= 1 do

228
begin
write(m);
m:=m-1;
end;
while k <= i do
begin
write(k);
k:=k+1;
end;
end; (* Цикл *)
begin
write('Уведіть n:'); readln(n);
writeln('Поки:');
iterat(n);
writeln;
writeln('Рекурсія');
reсursion(n);
end.
C++ Python
void reсursion(inti) def reсursion(i):
{ global s
if (i> 1) if i > 1:
reсursion(i – 1); reсursion(i – 1)
printf("%d", i); s+=str(i)
}
void iterat(inti) def iterat(i):
{ global s
int k = 1; k=1
while (k <= i) while (k <= i):
{ s+=str(k)
printf("%d", k); k += 1
k += 1; print("Уведіть n : ")
} n = int(input())
} s="Поки:\n"
int main() iterat(n)
{ print(s)
setlocale(LC_ALL, "rus"); s="Рекурсія\n"
int n; reсursion(n)
printf("Уведіть n : "); print(s)
scanf("%d", &n);

229
printf("Поки:\n");
iterat(n);
printf("\nРекурсія\n");
reсursion(n);
printf("\n");
system("pause");
return 0;
}

Часто зовнішня "люб'язність" рекурсії обертається великими


неприємностями.
Приклад 2.13. Обчислення факторіала числа.
Розберемося детально в роботі цієї процедури, для чого знову
звернемося до математики.
Для обчислення факторіала числа n, тобто n!, треба помно-
жити послідовно n натуральних чисел від 1 до n:
n ! = 1 ⋅ 2 ⋅ 3 ⋅ ... ⋅ n .
Наприклад, 4! дорівнюватиме: 4 ! = 1 ⋅ 2 ⋅ 3 ⋅ 4 = 2 4 .
Це прямий шлях обчислення, або ітеративний.
Є інший шлях обчислення: можна записати, що n! = N * (n-1)!
Такий шлях можна назвати зворотним, або рекурсивним.
Дамо рекурсивне визначення факторіалу числа. Позначимо
значення факторіала через fac.
Якщо n = 1 або n = 0 то fac: = 1
Інакше fac: = n * fac (n-1)

Нижче наведено функцію обчислення факторіала числа:

Псевдокод
const n=4;
function fac(n: byte):byte;
begin
if (n = 0) or (n = 1)
then fac:=1
else fac:=n*fac(n-1)
end;
begin
writeln('fac=',fac(n));
end.

230
C++
int fac(intn)
{
if ((n == 0) || (n == 1))
return 1;
else
returnn * fac(n – 1);
}
int main()
{
constint n = 4;
printf("fac = %d", fac(n));
return 0;
}
Python
def fac(n):
if (n == 0) | (n == 1):
return 1
else :
returnn * fac(n – 1)
n=4
print("fac =", fac(n))

Нехай уведено в програму значення 4 для обчислення факто-


ріала 4! Розглянемо, як працюватиме функція.
Після початку її роботи буде виконана перевірка:
Псевдокод
if (n = 0) or (n = 1) then f:=1
С++
if ((n == 0) || (n == 1)) return 1;
Python
if (n == 0) | (n == 1): return 1

Зрозуміло, що 4 не дорівнює 0 і не дорівнює 1, отже, буде ви-


конуватися оператор після else, тобто N * fac (n – 1), а це озна-
чає, що знову буде викликана та сама функція, але значення n
зменшиться на одиницю й дорівнюватиме 3; потім знову буде
виконана перевірка умови (n = 0) or (n = 1) і перехід до виклику
функції n * fac (n – 1).

231
Значення n зменшиться на 1 і дорівнюватиме 2 і т. д. до тих
пір, поки n не стане рівним 1, при цьому fac отримає значення 1.
Потім почнеться зворотний процес. Керування кожного разу
після завершення поточного виклику процедури буде передава-
тися оператору fac: = n * fac (n – 1). Весь зворотний процес від-
буватиметься так:
fac:=2*1; fac:=3*2; f:=6*4.
Образно кажучи, при прямому процесі значення n від 4 до 1
запам'ятовуються в стекову пам'ять, а при зворотному – значен-
ня n зчитуються зі стекової пам'яті та множаться на значення
fac. Схематично це можна зобразити так: значення n запам'ято-
вуються в стек-пам'ять:

4
3 4
2 3 4
1 2 3 4

а потім зчитуються у зворотному порядку, починаючи з 1.


Обов'язковим елементом в описі будь-якого рекурсивного
процесу є деяке твердження, що визначає завершення рекурсії.
У нашому випадку це оператор:
Псевдокод
if (n = 0) or (n = 1) then f:=1
С++
if ((n == 0) || (n == 1)) return 1;
Python
if (n == 0) | (n == 1): return 1

Для чіткого розуміння, що відбуваються в цьому процесі, не-


обхідно мати на увазі, що:
а) при кожному виклику процедури створюється новий екзе-
мпляр n;
б) усі екземпляри n накопичуються у внутрішньому стеку;
в) у будь-який момент обробки доступний тільки один, хро-
нологічно останній екземпляр n, який по завершенню чергового
рекурсивного виклику автоматично знищується.

232
Отже, при кожному входженні в рекурсивну підпрограму її
локальні змінні розміщуються в особливим чином організованій
області пам'яті – програмному стеці.
Рекурсивна форма організації підпрограми зазвичай має ви-
тонченіший вигляд порівняно з послідовною й дає компактні-
ший текст програми, але при виконанні зазвичай повільніша та
може викликати переповнення стека.
Як ми вже зазначали, при виклику функцій відбувається ро-
бота зі спеціальною пам'яттю – стеком. Обчислимо, скільки ра-
зів можна записувати в неї значення, не викликавши ситуацію
переповнення.
Приклад 2.14. Нескінченна рекурсія, за допомогою якої мо-
жна встановити, наскільки великий стек. Програма буде пере-
рвана з видачею повідомлення про помилку "Error: stack
overflow error (" Помилка: переповнення стека ").
Псевдокод
Programstack_test;
{програма перевірки стека}
procedure proc(i:integer);
begin
writeln(i);
proc(i+1);
end;
begin
proc(1);
end.
С++ Python
void proc(inti) def proc(i):
{ print(i)
printf("%d\n", i); proc(i + 1)
proc(i + 1); proc(1)
}
int main()
{
proc(1);
return 0;
}

Останнє значення, яке роздрукує програма перед перепов-


ненням стека, – Pascal 2643, C++ ~4760, Python 1042.
233
Легко складати рекурсивні процедури, якщо завдання пов'я-
зані з арифметичними або геометричними прогресіями, послідо-
вностями (до них ми повернемося пізніше), заданими як форму-
лами n-го члена, так і рекурентними співвідношеннями.
Розглянемо ще кілька прикладів простих програм з рекурси-
вними процедурами.
Приклад 2.15. Багатий дядечко подарував племіннику 1 дол у
перший день народження. У кожен наступний день народження він
подвоював подарунок і додавав до нього стільки доларів, скільки
років виповнилося племіннику. Написати програму, що підраховує
загальну суму грошей, подарованих до n-го дня народження, і вка-
зує, до якого дня народження сума подарунка перевищить 100 $.
Спочатку напишемо програму, скільки грошей отримає пле-
мінник до n-го дня народження.
Знову виконаємо рекурсивну процедуру, хоча можливий ін-
ший шлях розв'язання. Уведемо позначення: k – кількість років
племінника, p – кількість грошей, які дає дядько на кожен день
народження, s – загальна сума грошей, отриманих племінником
за всі роки, n – лічильник кількості днів народження, який рахує
у зворотному порядку від n (уведеного користувачем) до 1.

Процедура
Псевдокод
Procedure uncle(k, p, s, n: longint); {uncle – дядько}
begin
if n = 1
then write(s)
else
begin
k:=k+1;
p:=2*p+k;
uncle(k, p, s+p, n- 1)
end
end;
С++
void uncle(longintk, longintp, longints, intn)
{
if (n == 1)
printf("%ld", s);
else

234
{
k = k + 1;
p = 2 * p + k;
uncle(k, p, s + p, n – 1);
}
}
Python
def uncle(k, p, s, n):
if n == 1 :
print(s)
else :
k=k+1
p=2*p+k
uncle(k, p, s + p, n – 1)

Задаються початкові значення формальним параметрам проце-


дури: k: = 1; p: = 1; s: = 1; n уводиться користувачем із основної
програми (у ній, як і в попередній процедурі, немає вихідних пара-
метрів і змінних у самій процедурі, хоча можливі інші варіанти).
Збільшується кількість років: k: = k + 1; обчислюється пода-
рунок до k-го дня народження: p: = 2 * p + k; викликається про-
цедура, у якій збільшується на p загальна сума отриманих гро-
шей s і зменшується на 1 кількість днів народження:
uncle (k, p, s + p, n- 1)
Далі процес повторюється доти, поки n не дорівнюватиме 1.

Програма
Псевдокод
Program Rich_man1; { richman – багатий }
var
n: integer;
Procedure uncle(k, p, s, n: longint); {uncle – дядько}
begin
if n = 1
then write(s)
else
begin
k:=k+1;
p:=2*p+k;

235
uncle(k, p, s+p, n- 1)
end
end;
begin
write('Уведіть кількість років племінника '); readln(n);
write('Я отримаю к ', n, '-му дню народження ');
uncle(1, 1, 1, n);
writeln(' доларів')
end.
С++
void uncle(longintk, longintp, longints, intn)
{
if (n == 1)
printf("%ld", s);
else
{
k = k + 1;
p = 2 * p + k;
uncle(k, p, s + p, n – 1);
}
}
int main()
{
int n;
printf("Уведіть кількість років племінника "); scanf("%d",
&n);
printf("Я отримаю к %d-му дню народження ", n);
uncle(1, 1, 1, n);
printf(" доларів\n");
system("pause");
return 0;
}
Python
def uncle(k, p, s, n):
ifn == 1 :
print(s," доларів")
else :
k=k+1
p=2*p+k
uncle(k, p, s + p, n – 1)

236
print("Уведіть кількість років племінника "); n = int(input())
print("Я отримаю к ", n, "-му дню народження ")
uncle(1, 1, 1, n)

У другій частині умови треба визначити кількість років, коли


сума отриманих грошей буде рівною або перевищить 100 дол.
Для цього в процедурі змінюється опорна умова: if s> = 100 then
write (n), а все інше залишається без змін.

Псевдокод
Program Rich_man2;
Var n: integer;
Procedure uncle1(k, p, s, n: longint);
begin
if s >= 100
then write(n)
else
begin
k:=k+1;
p:=2*p+k;
uncle1(k, p, s+p, n+1)
end
end;
begin
write('Сума подарунка перебільшить 100 доларів к ');
uncle1(1, 1, 1, 1);
writeln('-му дню народження')
end.
С++
void uncle(longintk, longintp, longints, intn)
{
if (s>= 100)
printf("%d", n);
else
{
k = k + 1;
p = 2 * p + k;
uncle(k, p, s + p, n + 1);
}

237
}
intmain()
{
printf("Сума подарунка перебільшить 100 доларів к ");
uncle(1, 1, 1, 1);
printf("-му дню народження'\n")
return 0;
}
Python
def uncle(k, p, s, n):
ifs>= 100 :
print(n,"-му дню народження")
else :
k=k+1
p=2*p+k
uncle(k, p, s + p, n + 1)
print("Сума подарунка перебільшить 100 доларів к ");
uncle(1, 1, 1, 1)

Приклад 2.16. Написати процедуру переведення десяткового


числа у восьмеричне.
Рекурсивна процедура convert переводить десяткове число z
у восьмеричну систему шляхом розподілу його на 8 і видачі
залишку у зворотній послідовності.

Псевдокод
Programdezimal_oktal_konvertierung;
{переведення з десяткової системи у восьмеричну}
var z:integer;
procedure convert(z:integer);
begin
if z > 1 then convert(z div 8);
write(z mod 8:1);
end;
begin
writeln('Уведіть деяке натуральне число:'); readln(z);
writeln('Десяткове число:',z:6);
write('Восьмеричне число: ');
convert(z);
end.

238
С++
void convert(intz)
{
if( z> 1)
convert(z / 8);
printf("%d", z % 8);
}
intmain()
{
intz;
printf("Уведіть деяке натуральне число: "); scanf("%d", &z);
printf("Десяткове число : %6d\n", z);
printf("Восьмеричне число : ");
convert(z);
printf("\n");
return 0;
}
Python
def convert(z):
global s
if z> 1:
convert(int(z / 8))
s += str(z % 8)

print("Уведіть деяке натуральне число: ")


s = ""
z = int(input())
print("Десяткове число :", z)
convert(z)
print("Восьмеричне число :",s)

Розглянемо складнішу задачу, для складання програми якої


використовується рекурсивна процедура.
Приклад 2.17. Множачи великі числа, можна швидко отри-
мати переповнення. Тому для того, щоб надрукувати добуток,
який перевищує найбільше допустиме для даного цілого типу
(integer або longint) число, треба застосувати штучні засоби.
Складемо програму для друкування добутку двох чисел, який
може перевищувати максимально допустиме ціле число.

239
Процедура multiplication множить число a на кожну цифру чи-
сла b, починаючи з правої цифри. Остання цифра отриманого до-
бутку, складена із останньою цифрою наявного в пам'яті частко-
вого добутку, друкується, а всі інші цифри запамятовуються й пе-
редаються як параметри при рекурсивному зверненні до процеду-
ри multiplication. Наприкінці виконується множення на першу (лі-
ву) цифру числа b. На цьому множення закінчується. Тоді друку-
ється початок результату – накопичений частковий добуток без
останньої цифри (s div 10), а після нього, при поверненні з рекурсії,
– усі інші цифри добутку (s mod 10) у зворотному порядку порів-
няно з тим, як вони обчислювалися при входженні в рекурсію.

Процедура
Псевдокод
Procedure multiplication(a, b, s: longint);
begin
if b <> 0 then
begin
s:=s+a*(b mod 10);
multiplication(a, b div 10, s div 10);
write(s mod 10:1)
end
else if s <> 0 then write(s)
end;
C++
void multiplication(longinta, longintb, longints)
{
if (b != 0)
{
s = s + a * (b % 10);
multiplication(a, b / 10, s / 10);
printf("%ld", s % 10);
}
elseif (s != 0)
printf("%ld", s);
}
Python
def multiplication(a, b, s):
ifb != 0 :

240
s = s + a * (b % 10)
multiplication(a, int(b / 10), int(s / 10))
print(s % 10)
elifs != 0 :
print(s)

Програма
Псевдокод
Program Problem13; {Великий добуток}
var
x, y: longint;
Procedure multiplication(a, b, s: longint);
begin
if b <> 0 then
begin
s:=s+a*(b mod 10);
multiplication(a, b div 10, s div 10);
write(s mod 10:1)
end else if s <> 0 then write(s)
end;
begin
write('Уведіть перший множник '); readln(x);
write('Уведіть другий множник '); readln(y);
write(x,'*',y:1,' = ');
if ((x < 0) and (y > 0)) or ((x > 0) and (y < 0)) then write('-');
multiplication(abs(x), abs(y), 0);
end.
С++
void multiplication(longinta, longintb, longints)
{
if (b != 0)
{
s = s + a * (b % 10);
multiplication(a, b / 10, s / 10);
printf("%ld", s % 10);
}
elseif (s != 0)
printf("%ld", s);
}

241
int main()
{
longint x, y;
printf("Уведіть перший множник "); scanf("%ld", &x);
printf("Уведіть другий множник "); scanf("%ld", &y);
printf("%d * %ld = ", x, y);
if (((x < 0) && (y > 0)) || ((x > 0) && (y < 0)))
printf("-");
multiplication(abs(x), abs(y), 0);
printf("\n");
return 0;
}
Python
def multiplication(a, b, s):
global result
ifb != 0 :
s = s + a * (b % 10)
multiplication(a, int(b / 10), int(s / 10))
result+=str(s%10)
elif s != 0 :
result+=str(s)
print("Уведіть перший множник ")
x=int(input())
print("Уведіть другий множник ")
y=int(input())
result=""
if ((x < 0) & (y > 0)) | ((x > 0) & (y < 0)):
result+="-"
multiplication(abs(x), abs(y), 0);
print(x, "*", y, "=", result);

2.3. Непрямий виклик рекурсії


Рекурсивний виклик може бути непрямим. У цьому випадку
підпрограма звертається до себе опосередковано, шляхом ви-
клику іншої підпрограми, у якій міститься звернення до першої,
наприклад:

242
Псевдокод
Procedure A(i: integer);
begin
...
B(i);
...
end;
Procedure B(j: integer);
...
begin
...
A(j);
...
end;
С++ Python
void A(inti) def A(i):
{ ...
... B(i);
B(i); ...
... def B(i):
} A(i);
void B(inti)
{
A(i);
}

2.4. Хвостова та вкладена рекурсії


На перший погляд може здатися, що рекурсія – це просто
аналог циклічної організації алгоритму. Насправді ж ідея рекур-
сії набагато складніша і глибша.
Розглянемо приклад. Нехай є процедура fd (а), яка рисує ві-
дрізок довжиною а, і процедура rt (n), яка повертає напрямок
рисування на кут nо. Проаналізуємо, як працюватимуть такі
процедури.

243
Псевдокод
procedure SPIRAL1(a); procedure SPIRAL2(a);
begin begin
if a>10 then if a>10 then
begin begin
d(a); SPIRAL2(a-10);
rt(90); fd(a);
SPIRAL1(a-10); rt(90);
end end
end; end;
С++
void SPIRAL1(inta)
void SPIRAL2(inta)
{
{
if (a> 10){
if (a> 10){ SPIRAL2(a – 10);
fd(a); rt(90); SPIRAL1(a –
fd(a); rt(90);
10);
}
}
}
}
Python
def SPIRAL1(a): def SPIRAL2(a):
ifa> 10: ifa> 10:SPIRAL2(a-10)
fd(a) fd(a)
rt(90)SPIRAL1(a – 10) rt(90)

Бачимо, що процедура SPIRAL2 відрізняється від SPIRAL1


тільки тим, що, на відміну від SPIRAL1, у ній рекурсивний ви-
клик передує викликам
fd (a);
rt (90).
Отже, процеси виконання процедур якісно різні. Незважаючи
на це, ми отримуємо однаковий результат – красиву прямокутну
спіраль. Однак, якщо детально розібратися, то можна побачити,
що в першому випадку в процесі виконання процедури спіраль
закручується, а в другому – розкручується.
Процедура SPIRAL1 реалізує хвостову рекурсію, коли рекур-
сивний виклик є останньою командою процедури.
Якщо в процедурі рекурсивний виклик не є завершальною
інструкцією, то така рекурсія називається вкладеною.

244
2.5. Швидке піднесення до степеня
за допомогою рекурсії
Розглянемо ще один приклад. Нехай треба піднести число до
цілого степеня. У найпримітивнішому варіанті його можна прос-
то помножити само на себе потрібну кількість разів, але це – най-
повільніший зі способів. Ми скористаємося іншою ідеєю і побу-
дуємо рекурсивне визначення операції піднесення до степеня.
0. Якщо степінь дорівнює 0, то результат 1.
1. Якщо степінь, до якого треба піднести число, парний, то
піднесемо його до квадрата, а квадрат – до степеня вдвічі менше
первинного.
2. Якщо степінь, до якого потрібно піднести число, непарний,
то піднесемо число до парного степеня, який на одиницю мен-
ший ніж необхідний, способом, наведеним у кроці 1, а потім
просто помножимо результат на число.
При такому підході необхідна кількість множень скорочуєть-
ся вдвічі. Якщо ж організувати за цією схемою циклічні обчис-
лення, використовуючи її для піднесення квадрата числа до сте-
пеня, то ми досягнемо теоретично мінімальної кількості мно-
жень, можливої для цього завдання.
Наведемо тепер формальний алгоритм, а потім подивимося,
який елегантний вигляд усе матиме в рекурсивному стилі:
1. Умова виходу.
2. Якщо n парне, то.
3. Якщо n непарне, то.
Реалізація цього алгоритму мовами C++ та Python просто до-
слівно його повторює:
Псевдокод
function power(a:longint;x:word):longint;
begin
if x=0 then power:=1
else if x mod 2 = 0 then power:=power(sqr(a),x div 2)
else power:=a*power(sqr(a),(x-1) div 2);
end;
С++
longint power(longinta, unsignedintx)
{
if (x == 0) return 1;

245
elseif (x % 2 == 0) return power(a * a, x / 2);
elsereturna * power(a * a, (x – 1) / 2);
}
Python
def power(a, x):
if x == 0: return 1
elif: x % 2 == 0: return power(a * a, x / 2)
else: returna * power(a * a, (x – 1) / 2)

2.6. Ханойські вежі


У центрі світу у вершинах рівностороннього трикутника в
землю вбито три алмазні шпилі. На одному з них містяться 64
золоті диски з радіусами, що зменшуються (самий великий –
нижній). Працьовиті буддійські ченці день і ніч переносять дис-
ки з одного шпиля на інший. При цьому диски треба переносити
по одному й не можна класти більший диск на менший. Коли всі
диски перенесуть на інший шпиль, настане кінець світу (завдан-
ня і розповідь придумав математик Е. Люка в 1883 р.).
Розглянемо можливе розв'язання цієї задачі.
Ідея рекурсивного алгоритму не викликає труднощів. Він
дуже зрозумілий. Підійдемо до нього спочатку неформально.
Якщо ми маємо один диск, то просто переносимо його зі шпиля
a на шпиль b і закінчуємо роботу. В іншому випадку беремо
зверху зі шпиля стос із n-1 диска і весь переносимо на допоміж-
ний шпиль c. Потім найбільший диск, що залишився, переноси-
мо на кінцевий шпиль b. Тепер перенесемо знову стос із n-1
диска з допоміжного шпиля c на кінцевий шпиль b.
Якщо дати рекурсивне визначення задачі про Ханойські вежі,
то отримаємо таке:
Якщо кількість дисків n дорівнює 1, то перенести диск зі
шпиля а на кінцевий шпиль b.
В іншому випадку виконати дії:
Перенести тимчасово стос із n-1 диска зі шпиля a на шпиль
c, використовуючи як допоміжний шпиль b.
Перенести один диск зі шпиля a на шпиль b.

246
Перенести стос із n-1 диска зі шпиля c на шпиль b, викорис-
товуючи як допоміжний шпиль a.
Рекурсивність визначення полягає в тому, що для перенесен-
ня n-1 диска на тимчасовий шпиль і з нього використовуються ті
самі визначення, тільки кількість дисків зменшується з кожним
перенесенням стосу. Отже, досягнемо значення n = 1, і рекурсія
зупиниться.
Тепер, припускаючи, що всі диски лежать правильно, тобто
менший на більшому, і що інші диски на всіх трьох шпилях не
менше верхніх m на шпилі а, легко скласти програму (ми вже
знаємо, що, написавши рекурсивне визначення завдання, напи-
сати програму не становить труднощів).
Нехай Р (m, а, b, с) – процедура, яка переносить m дисків зі
шпиля a на шпиль b, використовуючи як допоміжний шпиль c.
Для програмування позначимо шпилі а, b, с цифрами 0, 1, 2 і
будемо друкувати потрібні перенесення дисків. Перенесення зі
шпиля а на b зображатимемо як 0 → 1, з b на c – як 1 → 2 і т. д.
Отримаємо таку програму:

Псевдокод
programHANOJ;
var n: integer;
procedure P(m, a, b, c: integer);
begin
if m=1 then writeln(a,' → ', b,' ') {переносимо один}
else
begin
P(m-1, a, c, b); {переносимо стос із m-1 верхніх
дисків}
P(1, a, b, c); {переносимо один найбільший}
P(m-1, c, b, a) {переносимо стос із m-1 верхніх дис-
ків}
end
end;
begin
readln (n); writeln('n =', n);
P(n, 0, 1,2)
end.

247
C++
void P(intm, inta, intb, intc)
{
if (m == 1)
printf("%d -> %d\n", a, b); // переносимо один
else
{
P(m – 1, a, c, b);//переносимо стос із m – 1 верхніх
дисків
P(1, a, b, c);//переносимо один найбільший
P(m – 1, c, b, a);//переносимо стос із m – 1 верхніх
дисків
}
}
int main()
{
int n; scanf("%d", &n);
printf("n = %d\n", n);
P(n, 0, 1, 2);
return 0;
}
Python
def P(m, a, b, c):
global s
if (m == 1):
s+=str(a)+" -> "+str(b)+" "
else:
P(m – 1, a, c, b)
P(1, a, b, c);
P(m – 1, c, b, a)
n = int(input())
s = "n = "+str(n)+"\n"
P(n, 0, 1, 2)
print(s)

При n = 4 програма надрукує такі правила перенесення дисків:


0→ 2 0→ 1 2→ 1 0→ 2 1→ 0 1→ 2
0→ 2 0→ 1 2→ 1 2→ 0 1→ 0 2→ 1
0→ 2 0→ 1 2→ 1

248
2.7. Нерекурсивний метод
Для того щоб реалізувати ітераційне розв'язання, досить
отримати формули, що визначають вихідний і приймальний
стрижні залежно від номера кроку. Простий аналіз показує, що
рекурсивні виклики відрізняються один від одного значеннями
параметрів, що визначають кількість оброблюваних дисків, і
шпилями, використовуваними для перенесення на кожному
кроці. А що станеться, якщо вдасться позбутися цих параметрів?
Тоді рекурсивне розв'язання запишемо таким чином:
Побудувати вежу =
побудувати вежу
Перенести диск з одного стрижня на інший
Побудувати вежу.
Отже, є три хвостові рекурсії, які легко зводяться до ітерацій:
Побудувати вежу =
Задану кількість разів, а саме: 2 N − 1 перенести диск з одно-
го стрижня на інший.
Подібні прийоми широко використовуються при розробці
трансляторів і детально описані.
Шпилю, на якому диски містяться спочатку, дамо номер 0;
шпилю, на який їх треба перенести – номер 2; відповідно остан-
ньому шпилю – номер 1.
Нехай усього дисків N. Пронумеруємо їх у порядку збіль-
шення радіуса числами 0, 1, 2, ..., 2 N − 1.
Будь-яке натуральне число i зобразимо у вигляді
i = (2t + 1)*2k , де t і k – цілі числа (тобто як добуток непарного
числа на деякий степінь двійки). На i-му ході переноситься диск
із номером k зі шпиля з номером (−1) N −k * t mod3 на шпиль із
N −k
номером (−1) *(t + 1)mod3 .
Приклад для N = 4.
Хід Диск t Зі Hа
шпиля шпиль
1 0 0 0 1
2 1 0 0 2
249
3 0 1 1 2
4 2 0 0 1
5 0 2 2 0
6 1 1 2 1
7 0 3 0 1
8 3 0 0 1
9 0 4 1 2
10 1 2 1 0
11 0 5 2 0
12 2 1 1 2
13 0 6 0 1
14 1 3 0 2
15 0 7 1 2
Якщо уявити, що шпилі, на які одягаються диски, розташо-
вані в кутах рівностороннього трикутника, то найменший диск
при кожному непарному ході рухається за (або проти, що зале-
жить від початкової кількості дисків) годинниковою стрілкою.
Усі парні ходи визначаються однозначно: спочатку перекла-
дається менший диск, оскільки не можна чіпати диск 0 і класти
більший на менший.
Зазначимо дві закономірності:
1) На кожному непарному ході відбувається перенесення
найменшого диска.
2) Найменший диск завжди переноситься циклічно: або
A → B → C → A → B → C → ...
(у разі парної кількості дисків), або
A → C → B → A → C → B → ...
(у разі непарної).
Маємо алгоритм:
1. Визначаємо кількість дисків, тобто як буде переміщатися най-
менший диск (цей крок виконується на початку, до того ж один раз).
2. Зазначаємо номер ходу: якщо непарний – переносимо най-
менший диск у напрямку, визначеному в п. 1; якщо парний – то
можливий один хід – беремо найменший із двох верхніх дисків і
переносимо його.
Можна написати трохи по-іншому.

250
Для N від 1 до 2k − 1 виконувати:
1. У двійковому поданні N знайти найправіший ненульовий
розряд. Позначимо його номер t.
2. Позначимо номер шпиля, на якому міститься диск t, через
t
i. Перемістити диск t зі шпиля i на шпиль (i + (−1) )mod3 .
До речі, за номером ходу легко можна відновити положення
дисків на шпилях: після i-го ходу диск із номером j міститься на
n− j j j +1
шпилі з номером (−1) ((i div 2 ) − (i div 2 )) mod 3 .
У загальному випадку рекурсія може бути успішно застосована
для обробки об'єктів, частини яких мають той самий тип, що й сам
об'єкт (будь-яка частина дерева – дерево, будь-яка частина масиву –
масив тощо). Слід звертати особливу увагу на умову виходу: якщо
вона задана некоректно, то підпрограма зациклиться. Інша важлива
властивість – глибина рекурсії, тобто кількість викликів підпрогра-
мою самої себе до спрацьовування умови виходу. Не передавайте
рекурсивним процедурам громіздкі об'єкти (масиви тощо). Для об-
робки таких структур або оголошуйте їх глобально, або розміщуйте
у пам'яті динамічно, а покажчик передавайте на масив.

2.8. Розширений синтаксис


виклику функцій
У C++ і Python є можливість викликати функцію й не викорис-
товувати те значення, яке вона повертає. Іншими словами, виклик
функції може зовні мати вигляд виклику процедури, наприклад:

Псевдокод
Function Func (var x: Integer): Integer;
begin
if x<0 then x:=0
else Func:=x+10
end;
var i: integer;
begin

251
i:=1;
i:=2*Func(i)-100; {Стандартний виклик функції}
Func(i) {Розширений синтаксис виклику}
end.
С++
int Func(int *x)
{
if (x< 0) *x = 0;
elsereturn *x + 10;
}
int main()
{
int i;
i = 1;
i = 2 * Func(&i) – 100; // Стандартний виклик функції
Func(&i); // Розширений синтаксис виклику
return 0;
}
Python
def Func(x):
if x[0] < 0:
x[0] = 0
else:
returnx[0] + 10

i = [1]
i[0] = 2 * Func(i) – 100; # Стандартний виклик функції
Func(i) # Розширений синтаксис виклику

Вправи
1. Нарисувати такі фігури:

252
Вказівка. Дати рекурсивне визначення: наприклад, це прави-
льний шестикутник, на кожній стороні якого нарисований пра-
вильний шестикутник, на кожній стороні якого нарисований
правильний шестикутник і т. д.
2. Написати рекурсивні функції Fact (N) і Fact2 (N) дійсного
типу, які обчислюють значення факторіала N! і подвійного фак-
торіала N !!, відповідно (N> 0 – параметр цілого типу). За допо-
могою цих функцій обчислити факторіали та подвійні факторіа-
ли п'яти даних чисел.
3. Написати рекурсивну функцію PowerN (x, n) дійсного типу,
яка знаходить значення n-го степеня числа x за формулою:
x0 = 1, xn = x·xn–1 при n > 0 xn = 1 / x–n/xn при n < 0 (x > = 0 –
дійсне число, n – ціле). За допомогою цієї функції знайти зна-
чення xn при 5 різних значеннях n.
4. Написати рекурсивну функцію SqrtK (x, k, n) дійсного ти-
пу, яка знаходить наближене значення кореня k-го степеня з
числа x за формулою: y(n+1) = y(n) – (y(n) – x/y(n)k–1)/k, де y(n)
позначає SqrtK (x, k, n) (x – речовинний параметр, k та n – цілі;
x > 0, k > 1, n > 0). За допомогою цієї функції знайти наближені
значення кореня k-го степеня з x за шести різних значень n для
даних x та k.
5. Написати рекурсивну функцію FibRec (N) цілого типу, яка
обчислює N-те число Фібоначчі F (N) за формулою: F (1) = F (2)
= 1, F (k) = F (k-2) + F (k -1), k = 3, 4, .... За допомогою цієї функ-
ції знайти п'ять чисел Фібоначчі із зазначеними номерами й ви-
вести ці числа разом з кількістю рекурсивних викликів функції
FibRec, які знадобились для їх знаходження.
6. Написати рекурсивну функцію C (m, n) цілого типу, яка
знаходить кількість поєднань із n елементів по m, використову-
ючи формулу: C (0, n) = C (n, n) = 1, C (m, n) = C (m, n-1) + C (m-
1, n-1) при 0 < m < n (m та n – цілі параметри; n > 0, 0 <= m <= n).
Дано число N і п'ять різних значень M. Вивести числа C (M, N)
разом з кількістю рекурсивних викликів функції C, які знадоби-
лись для їх знаходження.
Написати рекурсивну функцію NOD (A, B) цілого типу, яка
знаходить найбільший спільний дільник двох натуральних чисел
A та B, використовуючи алгоритм Евкліда: NOD (A, B) = NOD

253
(B mod A, A), якщо A <> 0; NOD (0, B) = B. За допомогою цієї
функції знайти найбільші спільні дільники пар A та B, A та C, A
та D, якщо дано числа A, B, C, D.
7. Написати рекурсивну функцію
MinRec(A,N)1|MaxRec(A,N)2
дійсного типу, яка знаходить мінімальний1/максимальний2 еле-
мент речового масиву A розміром N, не використовуючи опера-
тор циклу. За допомогою функції MinRec1|MaxRec2 знайти міні-
мальні1|максимальні2 елементи масивів A, B, C розміром NA,
NB, NC, відповідно.
8. Написати рекурсивну функцію Digits (S) цілого типу, яка
знаходить кількість цифр у рядку S без використання оператора
циклу. За допомогою цієї функції знайти кількість цифр у даних
п'яти рядках.
9. Написати рекурсивну функцію Simm (S) логічного типу, яка
перевіряє, чи є симетричним рядок S, без використання оператора
циклу. За допомогою цієї функції перевірити дані п'ять рядків.

2.9. Структурні типи даних


Раніше ми розглядали прості типи даних. Об'єкт даних будь-
якого простого типу може містити одне й тільки одне значення.
Однак для розв'язання більшості завдань за допомогою програм
потрібно мати можливість опису об'єктів даних, які зберігають
багато значень. Такі об'єкти описуються структурними типами
даних, які необхідні з двох причин. По-перше, для розробки про-
грам методом "зверху вниз" потрібно вміти описувати дані на
різних рівнях. З уточненням проекту внутрішня структура даних
буде все більше деталізуватися. Таким чином, початкова структу-
ра даних описується в термінах складних структур даних, які, у
свою чергу, описуються в термінах простіших структур даних до
тих пір, поки не вийде опис у термінах простих об'єктів даних, з
яких, за кінцевим рахунком, складається структура.
Розгляд різноманітних способів, якими нам хотілось би струк-
турувати дані, показує, що потрібні два різні механізми. По-перше,
механізм масивів, якій дозволяє групувати набір об'єктів ідентич-
254
ного типу таким чином, що кожен об'єкт вибирається за допомо-
гою індексу або номера. По-друге, механізм записів, якій дозволяє
групувати набір об'єктів різних типів таким чином, що вибір кож-
ного окремого об'єкта здійснюється за допомогою імені компонен-
та. Далі ми побачимо, що саме з другого механізму виникло об'єк-
тно-орієнтоване програмування. Ці два механізми можна комбіну-
вати при побудові структур даних довільної складності.
Крім механізмів масивів і записів, є механізм множин, що за-
безпечує зовсім інші можливості. Поки ми розглянемо механіз-
ми масивів, записів і множин для структурування даних.

2.9.1. Масиви

Розглянуті вище прості типи даних дозволяють використову-


вати в програмах поодинокі об'єкти, числа, символи тощо.
У Борланд Pythonі можуть використовуватись також об'єкти,
які містять багато однотипних елементів.
Масиви – це формальне об'єднання кількох однотипних об'-
єктів (чисел, символів тощо), яке розглядається як єдине ціле.
Масиви застосовують, коли потрібно зв'язувати й використо-
вувати цілу низку споріднених величин. Розглянемо це деталь-
ніше на прикладі числових масивів.
Приклад 2.18. Уявімо таку задачу: географ передав вам на-
бір показань температури, які вимірювались опівдні протягом
червня цього року. Він просить написати програму, яка проана-
лізує ці дані. Наприклад, йому хотілося б знати:
а) середню температуру в червні;
б) кількість днів, коли температура була вище 23о.
Для розв'язання задачі використаємо числові масиви.
Числовий масив – це набір чисел, якому дано одне спільне ім'я.
Кожне число в масиві називається елементом.
Набір температур утворює масив. Дамо цьому масиву ім'я t.
Тоді масив t містить 30 елементів, при цьому кожний елемент
зображує температуру одного з днів червня.
Елементи масиву t можна записати в такому вигляді:
t[0], t[1], t[2], t[3], ..., t[28], t[29].

255
Нижче показано, як призначаються елементи масиву для 30
чисел, що зображують червневі температури.

Дата Температура Елемент


1 ЧЕРВНЯ 15 t[0]
2 ЧЕРВНЯ 18 t[1]
3 ЧЕРВНЯ 23 t[2]
4 ЧЕРВНЯ 25 t[3]
5 ЧЕРВНЯ 24 t[4]
...... ....... ......
29 ЧЕРВНЯ 20 t[28]
30 ЧЕРВНЯ 21 t[29]

Наприклад, значення елемента t[2]=23, а значення елемента


t[28]=20. Важливо пам'ятати, що елементи t[0], t[1], t[2], ..., t[29]
є просто окремими числами.
Тепер напишемо програму для знаходження середнього зна-
чення температури й кількості днів з температурою більше 23о,
використовуючи масив t для зберігання набору температур.
У програмі ми маємо описати масив. Для цього існує два
способи. Перший виконується таким чином:

Псевдокод
var
<ім'я масиву>: array[n1. . n2] of<тип елементів масиву>.
Притримуючись зазначеної схеми, заданий масив опишемо так:
var
t: array[0..29] ofinteger;

С++
<тип елементів масиву><ім'я масиву>[кількість елементів масиву];
Притримуючись зазначеної схеми, заданий масив опишемо так:
int arr[30];

Python
Оголошення відбувається при ініціалізації, щоб ініціалізувати масив
із 30 елементів і заповнити його нулями, потрібно написати:
<ім'я масиву> = [0]*30

256
В описі <ім'я масиву> – це ідентифікатор, який задовольняє
всі потреби мови Python. Також необхідно вказати тип елементів
масиву (не тип індексів, а саме тип значення елементів).
Масив описано. Тепер опишемо інші змінні, що задіяні в на-
шій програмі.
Для організації циклів нам потрібен лічильник. Позначимо
його традиційно i – тип цілий; потрібен також лічильник кілько-
сті днів з температурою вище 23о, для нього створимо змінну k –
тип цілий; для підрахунку середнього значення температури
потрібно знайти її суму, для чого вкажемо змінну s, а оскільки в
ній буде зберігатись і середнє значення, то тип має бути дійсний
(ділення суми чисел на 30 може зумовити дробовий результат).
Отже, увесь опис буде таким:

Псевдокод
var
t : array[0. . 29] of integer;
i, k : integer;
s : real;
С++
int arr[30];
int i, k;
double s;
Python
arr = [0]*30
усі інші змінні до ініціалізації не оголошені

Другим способом записати масив чисел можна в такій формі:


Псевдокод
type
<ім'я типу>=array[n1..n2] of <тип елементів масиву>;
var
<ім'я масиву>: <ім'я типу>;
С++
typedef<тип елементів><ім'я типу>[<кількість елементів>];
<ім'я типу><назва масиву>;
Python
Python не має такого способу.

257
Для нашого прикладу це може бути записано так:
Псевдокод
type
a= array[0..29] of integer;
var
t: a;
С++
type def int a[30];
a arr2;
Python
Python не має такого способу.

Тепер починається основна програма, у якій передусім потрі-


бно присвоїти елементам масиву їхні значення. Для цього засто-
совується цикл від 0 до 29, де видається на екран запит про вве-
дення потрібного елемента (процедура write) і занесення його у
відповідну комірку пам'яті (процедура readln). Ця частина про-
грами матиме такий вигляд:

Псевдокод
for i:=0 to 29 do
begin
write('Введіть температуру в ',i+1, '- день ');
readln(t[i])
end;
С++
for (i = 0; i < 30; i++)
{
printf("Введіть температуру в %d день ", i + 1);
scanf("%d", &arr[i]);
}
Python
for i in range(30) :
print("Введіть температуру в", i+1, "день")
arr[i]=int(input())

За допомогою цього фрагмента програми на екран буде ви-


водитися 30 запитів про введення, і вам доведеться набирати з
клавіатури 30 чисел – щоденну температуру.

258
Наступний етап – це додавання температури й певної кілько-
сті днів з температурою вище 23о.
Зрозуміло, що перед початком циклу потрібно обнулити лі-
чильники s і k. Цей фрагмент програми буде таким:
Псевдокод
s:=0; k:=0;
for i:=0 to 29 do
begin
s:=s+t[i];
if t[i] > 23 then k:=k+1
end;
С++
s = 0; k = 0;
for (i = 0; i < 30; i++)
{
s += arr[i];
if (arr[i] > 23)
k++;
}
Python
s, k = 0, 0
for i in range(30) :
s += arr[i]
if arr[i]>23:
k+=1

Залишився останній крок – виведення результату на екран.


При цьому потрібно не забути розділити суму s на 30 (оскільки
знаходимо середню температуру). Це виконується таким чином:
Псевдокод
writeln('Середня температура в червні ', s/30:4:2);
writeln('Кількість днів з температурою більше 23о ', k)
С++
printf("Середня температура в червні %4.2lf\n", s / 30);
printf("Кількість днів з температурою вище 23о %d\n", k);
Python
s, k = 0, 0
for i in range(30) :
s += arr[i]
if arr[i]>23:
k+=1
259
Залишилось записати всю програму і виконати її:

Програма
Псевдокод
Program Temperature;
var
t: array[0..29] of integer;
i, k: integer;
s: real;
begin
for i:=0to 29do
begin
write('Введіть температуру в ',i,'- день ');
readln(t[i])
end;
s:=0; k:=0;
for i:=0 to 29 do
begin
s:=s+t[i];
if t[i] > 23 then k:=k+1
end;
writeln('Середня температура в червні ', s/30:4:2);
writeln('Кількість днів з температурою більше 23о ', k)
end.
С++
int main()
{
int arr[30];
int i, k;
double s;
for (i = 0; i < 30; i++)
{
printf("Введіть температуру в %d день ", i + 1);
scanf("%d", &arr[i]);
}
s = 0; k = 0;
for (i = 0; i < 30; i++)
{
s += arr[i];

260
if (arr[i] > 23)
k++;
}
printf("Cередня температура в червні %4.2lf\n", s / 30);
printf("Кількість днів з температурою вище 23о %d\n", k);
return 0;
}
Python
arr = [0]*30
for i in range(30) :
print("Введіть температуру в", i+1, "день")
arr[i]=int(input())
s, k = 0, 0
for i in range(30) :
s += arr[i]
if arr[i]>23:
k+=1
print("Cередня температура в червні","%4.2f"% (s / 30));
print("Кількість днів з температурою вище 23о", k);

Приклад 2.19. Створити програму підрахунку суми елемен-


тів масиву із 20 чисел, які мають парні порядкові номери.
Цей приклад ми розберемо переважно для того, щоб проде-
монструвати створення (заповнення) масиву за допомогою фун-
кції випадкових чисел. Для цього згадаємо запис і роботу деяких
функцій.
Функція random(x)(c++ rand()%x, randint(x)), де x – число ти-
пу word(с++ unsignedint), виробляє рівномірне псевдовипадкове
число з проміжку [0, x].
Зверніть увагу, що число х не входить у проміжок. Напри-
клад, функція random(30) буде виробляти випадкове ціле число з
проміжку [0, 29], тобто число 30 не буде входити в цей промі-
жок. Щоб 30 входило до складу випадкових чисел, потрібно
записати функцію так: random(31).
Щоб задати випадкове ціле число з проміжку [0, n], необхід-
но вказати функцію random(n+1), а для того, щоб ця функція
виробляла випадкове ціле число з проміжку [a, b], має бути
виконаний такий запис: random(b- a+1)+a.
261
Також у програму потрібно включити процедуру randomize
(c++ srand(), у Python ця процедура не потрібна), яка буде ініціа-
лізувати генератор випадкових чисел random(x); інакше можна
отримати при повторному виконанні програми той самий ре-
зультат, який було отримано при першому виконанні.
Для створення випадкових дійсних чисел можна використати
функцію random, яка видає випадкове дійсне число з проміжку [0, 1].
Тепер познайомимося зі ще одним дуже важливим поняттям –
це константи. Не важко помітити одну незручність, пов'язану з
використанням масивів у програмах мовою С++. Її суть полягає в
тому, що потрібно відразу жорстко фіксувати кількість елементів
масиву. Ми не можемо розглядати масиви, кількість елементів
яких визначаються в програмі. Отже, ми не можемо зробити запис
double arr[n], якщо n не задано перед цим. Так, у програмі про
температуру нам треба було точно вказувати в описі кількість
елементів масиву – 30 (int arr[30]), а в іншому прикладі – 20 еле-
ментів (int a[20];). Далі в програмі ми весь час указували кількість
елементів: у циклах із заповнення масивів, у робочих циклах про-
грами тощо. Тому програма може працювати тільки для 20 елеме-
нтів, хоча немає ніяких перешкод розглянути масиви з іншою кі-
лькістю елементів, наприклад 30, 40, 50, …, 100, 1000 тощо.
Для зміни кількості елементів масиву потрібно переглядати
всю програму і вносити зміни всюди, де вказана кількість еле-
ментів. Якщо необхідно вносити багато виправлень, то треба
спочатку знайти всі місця в програмі, які їх потребують. Якщо
програма велика, то велика і ймовірність пропустити такі місця.
У C++ є засіб, який дозволяє зменшити труднощі, пов'язані з
виправленнями програм – це константи.
Константа в програмі мовою С++ – це ідентифікатор, що є конк-
ретним числом, яке є значенням константи. Відмінність константи
від змінної полягає в тому, що її значення не можна змінити за до-
помогою операторів програми, а також у тому, що значення конста-
нти закріпляється в ній ще до виконання операторів, у розділі описів.
Константи не можуть бути в операторах присвоєння ліворуч
від символу =, але їх можна використовувати в описі масивів як
границі (попередньо кожна константа має бути описана в про-
грамі). Приклади опису:
Const int n = 100;

262
Такій опис може використовуватись для створення кількох
констант.
Const int n = 100; m = 25; k = 1000;
Опис констант виконується перед описом типу, тобто споча-
тку описуються константи, потім – типи, потім – змінні тощо.
Далі в програмах ми будемо використовувати константи.
Тепер повернемось до нашої задачі. У ній не вказані конкре-
тні 20 чисел, тому їх можна взяти випадково, що дає можливість
заповнити масив за допомогою функції випадкових чисел.
Отже, описуємо масив із 20 чисел і змінні, які будуть викори-
стовуватись у програмі:

Псевдокод
Const
n = 20;
type
t = array[0. . (n-1)] of integer;
var
a: t;
s, i: integer;
С++
Const int n = 20;
typedef int t[n];
t a;
int s, i;
Python
У Python немає змінних-констант, але можна зробити так:
n=20;
a=[0]*n
Або навіть так, що дозволяє задавати розмір масиву безпосередньо
при виконанні програми:
n=int(input())
a=[0]*n

Тепер створимо процедуру для введення, точніше, для ство-


рення масиву чисел із n (n = 20) елементів за допомогою функції
випадкових чисел з наступним їх виведенням на екран.

263
Розглянемо особливості побудови процедур.
Може скластися враження, що оголошення змінних у списку
формальних параметрів процедури нічим не відрізняється від
оголошення їх у розділі опису змінних. Насправді існує багато
спільного між описом змінних в основній програмі та формаль-
ними змінними в процедурі, але є одна дуже важлива різниця:
типом будь-якого параметра в списку формальних параметрів
процедури може бути тільки стандартний (int, float тощо) або
раніше оголошений користувацький тип. Оскільки в списку фо-
рмальних параметрів фактично оголошується тип-діапазон, який
указує границі індексів масиву, то не можна оголосити таке:

Псевдокод
Procedure create(a: array[0. . 19] of integer);
С++
void create(typedef int a[30])

Наприклад:
Псевдокод
const
n = 20;
type
t = array[0…(n-1)] of integer;
Procedure create(a: t);
С++
Const int n = 20;
Typedef int t[n];
void create(t a);
Python
У Python оголошувати нічого не потрібно
def create(a)

Отже, щоб виробити процедуру створення елементів масиву,


потрібно, щоб у розділі опису був описаний масив, а потім уже
за всіма попередніми вимогами створюється процедура:
Псевдокод
Procedure create(n: integer; vara: t);
var
i: integer;
begin
randomize;

264
writeln('Задано масив цілих чисел');
for i:=0 to n-1 do
begin
a[i]:=random(201)- 100;
write(a[i], ' ')
end;
writeln
end;
С++
#include<ctime>
void create(int n, ta)
{
srand(time(0));
printf("Задано масив чисел\n");
for (int i = 0; i <n; i++)
{
a[i] = rand() % 201 – 100;
printf("%d ", a[i]);
}
}
Python
Def create(n,a):
print("Задано масив цілих чисел")
for i in range(n):
a[i]=randint(-100,101)
print(a)

Як бачите, елементи не тільки виробляються, але й виводять-


ся на екран (команда write(a[i], ' '). Далі ми зможемо використо-
вувати цю процедуру для створювання елементів за допомогою
функції випадкових чисел.
Наступним кроком буде новий перегляд масиву за допомо-
гою циклу, перевірки номера елемента й додавання, якщо номер
парний. Такий цикл матиме вигляд:

Псевдокод
s:=0;
for i:=0 to n-1 do
if i mod 2 = 0 then s:=s+a[i];
writeln ('Сума елементів з парними номерами дорівнює ', s)
int s = 0;

265
С++
for (int i = 0; i < 20; i++)
if (i % 2 == 0) s += a[i];
printf ("Сума елементів з парними номерами дорівнює
%d\n", s);
Python
s=0
for i in range(n):
if i % 2 == 0 :
s += a[i]
print ("Сума елементів з парними номерами дорівнює", s);

Програма
Псевдокод
Program Problem2;
const
n = 20;
type
t = array[0..(n-1)] of integer;
var
a : t;
s, i: integer;
Procedure create(n: integer; var a: t);
var
i: integer;
begin
randomize;
writeln ('Задано масив цілих чисел');
for i:=0to n-1do
begin
a[i]:=random(201)-100;
write(a[i], ' ')
end;
writeln
end;
begin
create(n, a);
s:=0;
for i:=0 to n-1 do
if i mod 2 = 0 then s:=s+a[i];
writeln (Сума елементів з парними номерами дорівнює ', s)
end.

266
С++
#include<ctime>
Const int n = 20;
Typedef int t[n];
void create(int n, ta)
{
srand(time(0));
printf ("Задано масив цілих чисел\n");
for (int i = 0; i < n; i++)
{
a[i] = rand() % 201 – 100;
printf("%d ", a[i]);
}
printf("\n");
}
int main()
{
t a;
create(n, a);
int s = 0;
for (int i = 0; i < 20; i++)
if (i % 2 == 0)
s = s + a[i];
printf ("Сума елементів з парними номерами дорівнює
%d\n", s);
return 0;
}
Python
from random import randint
def create(n,a):
print ("Задано масив цілих чисел ")
for i in range(n):
a[i]=randint(-100,101)
print(a)
n=20
a=[0]*n
create(n,a)
s=0
for i in range(n):
if i % 2 == 0 :
s = s + a[i]
print ("Сума елементів з парними номерами дорівнює ", s)

267
Два цикли (заповнення масиву і додавання) можна об'єднати
в один, тоді програма буде значно кращою:
Псевдокод
Program Problem2;
const
n = 20;
type
t = array[0..(n-1)] of integer;
var
a: t;
s, i: integer;
begin
randomize;
writeln ('Задано масив цілих чисел');
s:=0;
for i:=0 to n-1 do
begin
a[i]:=random(201)-100;
write(a[i], ' ');
if i mod 2 = 0 then s:=s+a[i]
end;
writeln;
writeln ('Сума елементів з парними номерами дорівнює ', s)
end.
С++
#include<ctime>
const int n = 20;
typedef int t[n];
int main()
{
t a;
srand(time(0));
int s = 0;
printf ("Задано масив цілих чисел \n");
for (int i = 0; i < 20; i++)
{
a[i] = rand() % 201 – 100;
printf("%d ", a[i]);
if (i % 2 == 0)
s = s + a[i];
}
printf("\n Сума елементів з парними номерами дорівнює

268
%d\n", s);
return 0;
}
Python
From random import randint
n=20;
a=[0]*n
s=0
print ("Задано масив цілих чисел")
for i in range(n):
a[i]=randint(-100,101)
if i % 2 == 0 :
s = s + a[i]
print(a)
print ("Сума елементів з парними номерами дорівнює", s)

Дуже часто одновимірний масив називають вектором, тобто


будь-який одновимірний масив ми можемо розглядати як век-
тор, заданий у деякому n-вимірному просторі, де n – кількість
елементів масиву. Звідси походить назва масив-вектор.

2.9.2. Використання масивів


як параметрів підпрограм

Для передачі в підпрограму масиву потрібно спочатку описа-


ти користувацький тип. Наприклад:

Псевдокод
type
mas=array[0..9]of Real;
і тоді в процедурі можна вказувати такий опис:
Procedure S (a: mas);
С++
Typedef double mas[10];
і тоді в процедурі можна вказувати такий опис:
void S(mas)
Python
def S(a)

269
Відкритий масив є формальним параметром підпрограми,
який описує базовий тип елементів масиву, але не визначає його
розмірність і границі:

Псевдокод
Procedure Test(a: array of Integer);
С++
void S(int a[], int L)
Python
Def S(a)

Усередині підпрограми параметр а трактується як одновимір-


ний масив з нульовою нижньою границею. Верхня границя відкри-
того масиву повертається функцією HIGH (у Python – len(), у С++
немає аналога під звичайні масиви, потрібно передавати довжину).
Використовуючи 0 як мінімальний індекс і значення, що поверта-
ється наведеною вище функцією, як максимальний індекс, підпро-
грама може обробляти одновимірні масиви будь-якої довжини:

Псевдокод
Procedure ArrayPrint(a: array of Integer);
var
k: Integer;
begin
for k:=0 to High(a) do
Write(a[k]);
WriteLn
end;
С++
void ArrayPrint(int a[], int len)
{
srand(time(0));
printf("Заданий масив чисел\n");
for (int k = 0; k <len; k++)
{
printf("%d ", a[k]);
}
printf("\n");
}

270
Python
def ArrayPrint(a):
for i in range(len(a)):
print(a[i]);

Отже, ми не описували масив а з конкретним діапазоном змі-


ни його індексів, але роздрукували всі його елементи.
Приклад 2.20. Створіть програму перестановки m-го та k-го
елементів одновимірного масиву.
Якщо виконати відразу команду присвоювання a[5]:=a[8], то
значення елемента a[5] буде "стерте" і втрачене. Щоб цього не
сталося, потрібна ще одна команда, яка б "запам'ятовувала" зна-
чення одного з елементів, що переставляються .
Нехай це буде змінна з ім'ям p, тоді перестановка матиме вигляд:
p:=a[5] – "запам'ятовується" п'ятий елемент,
a[5]:=a[8] – на місце п'ятого елемента стає восьмий,
a[8]:=p – на місце восьмого елемента стає п'ятий.
Тепер легко створити процедуру перестановки елементів:
Псевдокод
Procedure transp_two(n, k, m: integer; var a: arrayofinteger);
var
i, p: integer;
begin
p:=a[k];
a[k]:=a[m];
a[m]:=p;
n:=high(a);
for i:=1 to n do write(a[i], ' ');
writeln;
end;
С++
void transp_two(intL, intk, intm, inta[])
{
int p = a[k];
a[k] = a[m];
a[m] = p;
for (int i = 0; i <L; i++)
printf("%d ", a[i]);
printf("\n");
}

271
Python
def transp_two(k, m, a):
p = a[k]
a[k] = a[m]
a[m] = p
for i inrange(len(a)):
print(a[i])

Повністю створимо програму, використовуючи вже відомі


процедури й відповідні до них виклики із основної програми.
Псевдокод
Program Problem3;
const
n = 10;
type
t = array[0..(n-1)] ofinteger;
var
a: t;
k, m: integer;
Procedure create(var a: t);
var
i: integer;
begin
randomize;
writeln('Заданий масив цілих чисел');
for i:=0 to (n-1) do
begin
a[i]:=random(201)- 100;
write(a[i], ' ')
end;
writeln
end;
Procedure transp_two(k, m: integer; var a: array of integer);
var
i, p: integer;
begin
p:=a[k]; a[k]:=a[m]; a[m]:=p;
for i:=0 to n-1 do write(a[i], ' ');
writeln
end;

272
begin
create(n, a);
write('Введіть номера елементів, що перестав.'); readln(k, m);
writeln('Масив після перестановки ', k, '- го і ', m, '-го елементів');
transp_two(k-1, m-1, a);
end.
C++
#include<ctime>
Const int n = 10;
Typedef int t[n];
void create(t a)
{
srand(time(0));
printf("Заданий масив цілих чисел \n");
for (int i = 0; i < n; i++)
{
a[i] = rand() % 201 – 100;
printf("%d ", a[i]);
}
printf("\n");
}
void transp_two(intk, intm, inta[])
{
int p = a[k]; a[k] = a[m]; a[m] = p;
for (int i = 0; i < n; i++)
printf("%d ", a[i]);
printf("\n");
}
int main()
{
t a;
int k, m;
create(a);
printf("Введіть номера елементів, що перестав.\n");
printf("Введіть k\n");
scanf("%d", &k);
printf("Введіть m\n");
scanf("%d", &m);
printf("Масив після перестановки %d-го і %d-го елемен-
тів\n", k, m);
transp_two(k – 1, m – 1, a);
return 0;
}

273
Python
from random import randint
def create(a):
print("Заданий масив цілих чисел")
for i in range(len(a)):
a[i]=randint(100,201)
print(a)
def transp_two(k, m, a):
p = a[k];a[k] = a[m]; a[m] = p
print(a)
n=10;
a=[0]*n
create(a)
print("Введіть номера елементів, що перестав.")
print("Введіть k")
k = int(input())
print("Введіть m")
m = int(input())
print("Масив після перестановки ", k, "-го і", m,"-го елементів")
transp_two(k – 1, m – 1, a);

2.9.3. Пошук і перестановка елементів масиву

Приклад 2.21. В одновимірному масиві необхідно знайти


номер заданого користувачем числа і переставити його на перше
місце, послідовно переставляючи із сусідніми елементами.
Для розв'язання цієї задачі після виведення масиву, створе-
ного за допомогою генератора випадкових чисел, потрібно
запитати користувача, який елемент він бажає переставити на
початок масиву.
Заданий масив необхідно вивести, щоб користувач зміг поба-
чити елементи й указати для перестановки той, який у ньому є.
При вказуванні елемента "всліпу" може виникнути ситуація,
коли такого елемента не буде й задача стане нерозв'язною, тому
вважатимемо, що такий елемент існує.
Після задання елемента комп'ютер має знайти його номер, а
потім відомим нам способом переставити на початок масиву.
Отже, нове в задачі – пошук номера заданого елемента.
274
Для цього потрібно організувати цикл за кількістю елементів
і порівнювати кожен елемент масиву із заданим числом. Коли
настане рівність – у якусь змінну запам'ятати номер елемента.
Розглянемо таку процедуру пошуку номера елемента в масиві:
Псевдокод
Procedure search_number(d: integer; a: t; var k: integer);
begin
k:=0;
repeat
k:=k+1
until a[k] = d
end;
С++
void search_number(int d, t a, int* k)
{
*k = 0;
do {
*k = *k + 1;
} while (a[*k] != d);
}
Python
def search_number(d, a, k):
k[0] = 0;
whileTrue:
k[0] = k[0] + 1;
if a[k[0]] == d:
break;

Повністю програму можна скласти із трьох процедур: ство-


рення масиву – create; пошуку номера елемента – search_number;
перестановки елемента на початок масиву – transp2begin:
Псевдокод
Program Problem5;
const
n = 20;
type
t = array[0..(n-1)] of integer;
var
a: t;
i, k, d: integer;

275
Procedure create(n: integer; var a: t);
var
i: integer;
begin
randomize;
writeln('Заданий масив цілих чисел');
for i:=0 to (n-1) do
begin
a[i]:=random(201)- 100; write(a[i], ' ')
end;
writeln
end;
Procedure search_number(d: integer; a: t; var k: integer);
var
i: integer;
begin
i:=0;
repeat
i:=i+1
until a[i] = d;
k:=i
end;
Procedure transp2begin(k: integer; var a: t);
var
i, p: integer;
begin
for i:=k downto 1 do
begin
p:=a[i-1]; [i-1]:=a[i]; a[i]:=p
end
end;
begin
create(n, a);
write('Введіть елемент, що переставляється '); readln(d);
search_number(d, a, k);
transp2begin(k, a);
writeln('Масив після перестановки елементів у початок');
for i:=1 to n do write(a[i], ' ');
writeln
end.

276
С++
#include<ctime>
const int n = 20;
typedef int t[n];
void create(ta)
{
srand(time(0));
printf("Заданий масив цілих чисел \n");
for (int i = 0; i < n; i++)
{
a[i] = rand() % 201 – 100;
printf("%d ", a[i]);
}
printf("\n");
}
void search_number(intd, ta, int* k)
{
*k = 0;
while (a[*k] != d) {
*k+= 1;
}
}
void transp2begin(intk, ta)
{
int p;
for (int i = k; i > 0; i–)
{
p = a[i – 1];
a[i – 1] = a[i];
a[i] = p;
}
}
int main()
{
t a;
int d, k;
create(a);
printf("Введіть елемент, що переставляється \n");
scanf("%d", &d);
search_number(d, a, &k);
transp2begin(k, a);
printf("Масив після перестановки елементів у початок \n");
for (int i = 0; i < n; i++)
printf("%d ", a[i]);

277
printf("\n");
return 0;
}
Python
From random import randint
def search_number(d, a, k):
k[0] = 0;
while a[k[0]] != d:
k[0] += 1;
def create(a):
print("Заданий масив цілих чисел")
for i in range(len(a)):
a[i] = randint(-100,101)
print(a)
def transp2begin(k, a):
for i in range(k,0,-1):
p = a[i – 1]
a[i – 1] = a[i]
a[i] = p
n=20
a=[0]*n
create(a)
print("Введіть елемент, що переставляється");
d=int(input())
k=[0]
search_number(d, a, k);
transp2begin(k[0], a);
print("Масив після перестановки елементів у початок")
print(a)

Тут k – номер елемента, який переставляється. У процедурі


transp2begin у циклі послідовно переставляються попередній і
наступний елементи таким чином, що масив поступово, по од-
ному елементу, зміщується праворуч.
Чому цикл продовжується не до 0, а до 1? Зверніть увагу на
індекси елементів, які ми переставляємо: a[i-1] та a[i], при i=1
вони отримують значення a[0] та a[1], тобто всі елементи, вклю-
чаючи перший, будуть задіяні в перестановці. Якби ж цикл про-
довжувався до 0, то ми мали б елементи з номерами a[-1] та a[0].
Однак елемента a[-1] у масиві немає, тому виникала б помилка.

278
2.9.4. Методи сортування елементів масиву

Метод бульбашки. Упорядкувати елементи масиву – це змі-


нити позиції його елементів за зростанням чи спаданням. Іноді
такий процес називають сортуванням. Розглянемо на прикладі,
як будуть упорядковуватись елементи.
Нехай у нас є масив з 10 чисел:
5, 10, 7, 9, 14, 6, 3, 8, 2, 5.
Необхідно впорядкувати його за зростанням. Позначимо за-
даний масив чисел а, тоді кожен його елемент матиме таке ви-
значення:
a[0] a[1] a[2] a[3] a[4] a[5] a[6] a[7] a[8] a[9]
Будемо притримуватись цього способу сортування. Почнемо
порівнювати із останнього елемента. Порівняємо його з передо-
станнім; якщо він менше передостаннього, то переставимо їх;
інакше продовжимо порівняння.
Порівняємо останній елемент а[9] з передостаннім a[8]: a[9] <
a[8], 5 < 2. Умова не виконується, отже, потрібно порівнювати еле-
менти, рухаючись до початку масиву, тобто до першого елемента.
Порівнюємо восьмий і сьомий елементи a[8] = 2 та a[7] = 8:
a[8] < a[7], 2 < 8. Умова виконується, отже, потрібно перестави-
ти ці елементи: : p:=a[8], p:=2,
a[8]:=a[7], a[8]:=8, a[9]:=p, a[9]:=2.
Після перестановки цих двох елементів масив стане таким:
a[0] a[1] a[2] a[3] a[4] a[5] a[6] a[8] a[7] a[9]
Коли перестановка виконана, процес перевірки продовжуєть-
ся. Таким чином, на першому місці після першого проходу буде
мінімальний елемент. Потім процес повторюється із останнього
елемента, але продовжується до другого елемента а[1], потім до
третього, четвертого тощо. Процес дуже схожий на спливання
повітряної бульбашки з води вгору, тому такий метод упорядку-
вання масивів називається сортування бульбашкою.
Створимо процедуру за цим принципом. Для процесу сорту-
вання потрібні два цикли. Перший – зовнішній, який забезпечує
поступове переміщення вгору до початку, тобто до першого
елемента, а другий – внутрішній, у якому відбувається порів-
няння елементів та їх перестановка в необхідних випадках.
Отримаємо таку процедуру:
279
Псевдокод
Procedure bubble(n: integer; var a: t);
var
i, j, p: integer; {Сортування елементів масиву}
begin
for i:=2 to n do
for j:=n downto i do
if a[j] < a[j- 1] then
begin
p:=a[j];
a[j]:=a[j-1];
a[j- 1]:=p
end;
writeln('Упорядкований за неспаданням масив');
for i:=1 to n do write(a[i], ' ');
writeln
end;
С++
void bubble(intn, ta)
{
int i, j, p;
for (i = 1; i < n; i++)
for (j = n; j >= i; j++)
if (a[j] <a[j – 1])
{
p = a[j];
a[j] = a[j – 1];
a[j – 1] = p;
}
printf("Упорядкований за неспаданням масив ");
for (i = 0; i<n;i++)
printf("%d ", a[i]);
printf("\n");
}
Python
def bubble(a):
for i in range(1,n):
for j in range(n, i-1, -1):
if a[j] < a[j – 1]:
p = a[j];
a[j] = a[j – 1];
a[j – 1] = p;
print("Упорядкований за неспаданням масив ")
print(a)

280
Вправи
1. Створити програму видалення k-го елемента з масиву a(n);
включення елемента d у k-ту позицію масиву a(n).
2. Змінити порядок елементів одновимірного масиву на обер-
нений.
3. В одновимірному масиві знайти кількість його від'ємних
елементів.
4. Дано масив c(n). Знайдіть кількість його додатних елемен-
тів і виведіть на екран їхні індекси та самі елементи.
5. Написати програму обрахунку суми тих елементів цілочи-
слового масиву n0, n1, ..., n100, які є парними числами.
6. Дано масив a1, a2, ..., a200. Написати програму створення
масивів b1, b2, ..., b100 та c1, c2, ..., c100, елементи яких дорівнюють
відповідно значенням a1, a3, ..., a199 та a2, a4, ..., a200.
7. Написати програму визначення суми непарних від'ємних
елементів у заданому цілочисловому масиві a1, a2, ..., an.
8. Дано масиви a1, a2, ..., an та b1, b2, ..., bm. Написати програму
побудови масиву c1, .... cm+n, елементи якого відповідно дорів-
нюють a1, ..., an, b1, ..., bm.
9. Написати програму виконання таких задач: у масиві чисел
a1, ..., an:
а) замінити число b на число c;
б) підрахувати кількість елементів ai, для яких виконано
q ≤ ai ≤ p, де p та q – задані числа;
в) знайти суму додатних і кількість від'ємних елементів;
г) знайти суму й добуток усіх елементів.
Для розв'язання всіх задач досить одного перегляду масиву
a1, ..., an (одного циклу).
10. Перемістіть елемент масиву a(n) праворуч (ліворуч) на m
позицій, при цьому m-й елемент із кінця масиву перемістіть у
початок (або навпаки).
11. Написати програму, після виконання якої стане відомо, чи
є в цілочисловому масиві c-1, c0, c1,..., c1000 два підряд нульові
елементи.
12. Створити програму побудови вектора a1, a2, ..., a200, елеме-
нти якого відповідно дорівнюють значенням b1, c1, b2, c2,..., b100,
c100, де b1, b2, ..., b100 та c1, c2, ..., c100 – вектори, що дані.

281
13. Написати програми, при виконанні яких у масиві a1, a2, ...,
an визначається кількість сусідніх:
а) двох додатних чисел;
б) двох чисел різного знака;
в) двох чисел одного знака, причому абсолютна величина
першого числа має бути більше другого числа.
14. Дано масив a1, a2, ..., a2n. Написати програму побудови ма-
сивів з елементами, що дорівнюють:
а) a1, an+1, a2, an+2, ..., an, a2n;
б) a2n, a1, a2n-1, a2, ..., an+1, an.
15. Написати програму знаходження найбільшого спільного
дільника елементів цілочислового масиву a1, a2, ..., an.
У програмі описати й використати процедуру знаходження
найбільшого спільного дільника двох цілих чисел.
Сортування простими вставками. Для викладення ідеї ме-
тоду сортування простими вставками знадобляться знання про
пошук і вставлення елемента в упорядкованому масиві. Спочат-
ку розв'яжемо таку задачу.
Приклад 2.22. Задання пошуку елемента в упорядкованому
масиві.
Нехай задано цілочисловий масив a1 < ... < an і ціле число b.
З'ясувати, чи входить число b у заданий масив, а якщо входить,
то яке значення номера елемента, що дорівнює цьому числу,
тобто знайти таке p, для якого ap = b.
Якщо масив невпорядкований, то єдиний спосіб пошуку – по
черзі порівнювати задане число b з елементами масиву, почи-
наючи з першого, і продовжувати доти, поки не натрапимо на
рівний числу b елемент.
Якщо виявиться, що після перегляду і порівняння всіх елеме-
нтів масиву елемента, що дорівнює числу b, не знайдеться, то
видати повідомлення, що такого елемента в масиві немає.
Однак для впорядкованих даних є метод, який дає можли-
вість значно швидше відшукати рівний числу b елемент (або
з'ясувати, що такого немає). За С. А. Абрамовим називатимемо
цей метод "розділяй і володарюй".

282
Ідея методу дуже проста. Вважатимемо, що масив упорядко-
ваний за зростанням, тобто ( a1 ≤ a2 ≤ ... ≤ an ).
Якщо в масиві непарна кількість елементів, то назвемо серед-
нім елемент, який міститься посередині масиву. Наприклад, якщо
кількість елементів 17, то середнім буде елемент під номером 9.
Якщо ж кількість елементів парна, наприклад 20, то середнім
вважатимемо елемент під номером, який отримаємо від цілочи-
слового ділення суми 1 + 20 на 2, тобто (1 + 20) div 2 = 10. Для
масиву з кількістю елементів 124 середнім буде елемент під
номером 62 (125 ділимо націло на 2) тощо.
Ідея методу.
1. Вибираємо середній елемент масиву і порівнюємо із зада-
ним числом b.
Випадок 1. Обраний середній елемент менше числа b, тоді
немає сенсу розглядати ліву частину масиву (там рівного елеме-
нта точно не буде), тому шукаємо в правій частині масиву.
Випадок 2. Обраний середній елемент більше або дорівнює
заданому числу b, тоді немає сенсу розглядати праву частину
масиву, пошук триває серед елементів лівої частини.
Таким чином, кількість випробувань скорочується вдвічі.
2. З тією половиною масиву, у якій установлено, що може
бути рівний елемент, виконується та сама процедура розподілу.
3. Процес розподілу триває. Нарешті настає момент, коли в
обраній частині масиву залишається один елемент (тобто номе-
ра елементів ліворуч і праворуч стануть рівними), тоді він є шу-
каним; якщо ж він не дорівнює заданому числу, то такого еле-
мента взагалі немає в масиві. Для кращого розуміння процесу
розглянемо простий приклад.
Приклад 2.23. Нехай задано масив з 10 упорядкованих за
зростанням чисел:
23 34 45 48 56 63 67 72 78 89
Треба знайти елемент масиву, рівний числу 78.
Нехай p – номер елемента лівої межі масиву, q – номер еле-
мента правої межі масиву, s – номер середнього елемента.
Знаходимо середній елемент. Їм буде елемент під номером
s = ( p + q) \ 2 = (1 + 10) \ 2 = 5 (знак "\" – знак цілочислового ді-
лення), тобто 56.
283
Порівняємо його з числом 78: 56 <78. Отже, шуканий еле-
мент міститься праворуч від 56.
Розглядаємо праву половину масиву, тобто елементи, що мі-
стяться праворуч від 56:
63 67 72 78 89
Тоді p = 6, q = 10.
Вибираємо середній елемент. Їм буде елемент під номером
s = (p + q) \ 2 = (6 + 10) = 8, рівний 72.
Порівнюємо: 72 <78; отже, знову шуканий елемент міститься
праворуч від 72, тобто в такій частині масиву:
78 89
9 10
p = 9, q = 10.
Знову знаходимо середній елемент. Їм буде елемент під но-
мером
s = (p + q) \ 2 = (9 + 10) \ 2 = 9.
Порівнюємо: 78 <78.
Умова не виконується. Значить, треба брати елементи масиву
ліворуч від числа 78.
Тоді p = 9, q = 9. Номери лівої та правої меж стали рівними,
цикл закінчується, шуканим елементом буде елемент під номе-
ром 9, що дорівнює 78.
Справді: 78 = 78.
Зверніть увагу, що при русі праворуч, тобто коли розглядає-
мо праву частину масиву, граничний елемент (він же колишній
середній, з яким порівнюємо) у цю частину масиву не включа-
ється, а при русі ліворуч середній елемент включається в решту.
Алгоритм
Спочатку за ліву і праву межі беремо значення 1 та n. Нехай
p та q позначають ліву і праву межі, тоді p: = 1, а q: = n; далі ви-
конується розподіл і крок за кроком межі зближуються. Процес
поділу має тривати до збігу меж, коли p = q, тобто цикл викону-
ється, поки ( p < q). Межі зближуються таким чином: нехай s –
номер середнього елемента, значення s – це ціла частина серед-
нього арифметичного меж p та q ( s := ( p + q ) div 2).

284
Якщо as < b, то треба змінити колишню нижню межу на
s + 1 ( p := s + 1), а верхня межа (q) залишається без змін (виби-
рається права частина масиву); інакше залишити без змін ниж-
ню межу (p), а верхню замінити на s (q := s).
Тепер можна записати алгоритм у вигляді процедури.
{Процедура швидкого пошуку номера елемента в упорядко-
ваному масиві}

Псевдокод
Procedure quick_search(int[] a; int n, int b,returnint p);
Int q,s;
begin
p:=1; q:=n;
while p < q do
begin
s:=(p+q) div 2;
if a[s] > b then p:=s+1
else q:=s
end
end;
Python C++
def quick_search(a, n, b, p): void quick_search (int[] a, int n, int b,
p = 1; int*p)
q = n; {
while p < q : int q, s;
s = (p + q) // 2; p = 1;
if a[s] > b : q = n;
p = s + 1; while (p < q)
else: {
q = s; s = (p + q) % 2;
return p if (a[s] > b)
p = s + 1;
else
q = s;
}
}

Приклад 2.24. Вставити в упорядкований за зростанням ма-


сив задане число, не порушуючи впорядкованості масиву.
285
На цьому прикладі спробуємо впорядкувати отримані знання
про складання програм і умовно розділимо його на окремі етапи.
Перший етап. Математичний аналіз задання і побудови ал-
горитму розв'язання (причому алгоритм може бути складений у
довільній формі, навіть у формі усних міркувань).
Другий етап. Схематичне складання програми.
Третій етап. Деталізація розробленої схеми програми.
Дотримуючись такої стратегії, складемо програму розв'язан-
ня запропонованого завдання.
1. Математичний аналіз і складання алгоритму.
Нехай задано впорядкований за зростанням масив, наприклад
з 10 чисел:
12 23 34 45 56 58 78 89 90 98
Числом, яке треба вставити в масив, нехай буде 75.
За допомогою відомого методу швидкого пошуку знаходимо,
що це число має бути вставлене за елементом масиву з номером
6, тобто за 58.
Далі необхідно переставити всі елементи масиву, починаючи
з номера 7, на 1 елемент праворуч. Процес перестановки ми вже
виконували, але в даному випадку не треба піклуватися про збе-
реження значення елемента під номером 11, оскільки його вза-
галі немає, точніше, він дорівнює нулю. Отже, перестановку
можна замінити простим переміщенням, тобто елементу під
номером 11 присвоїти значення елемента під номером 10, еле-
менту 10 – значення елемента 9 тощо, після чого на місце еле-
мента під номером 7 вставити задане число.
Зрозуміло, що при описі масиву слід урахувати, що він стане
на 1 елемент довшим, а значить, описати масив на одиницю
більший, ніж задана константа.
Тепер математичний аналіз виконано, алгоритм побудовано
(нехай навіть у формі звичайного словесного опису).
Схема програми:
Program Problem3;
const n = ...;
опис числового масиву з кількістю елементів на одиницю бі-
льшим за n, тобто [1..n + 1];
опис змінних, що беруть участь у програмі;
286
опис процедури швидкого пошуку номера елемента, за яким
треба вставити задане число;
процедура переміщення елементів масиву праворуч на 1 елемент;
begin
уведення елементів упорядкованого за зрост. масиву;
writeln ('Упорядкований масив чисел');
виведення масиву;
write ('Введіть число, яке треба вставити');
readln (b);
виклик процедури пошуку;
виклик процедури переміщення елементів масиву;
виведення нового масиву
end.
Як бачите, схема програми складається вже в більш упорядко-
ваній формі. Усі процедури та інші оператори нам уже знайомі,
але треба продумати складання процедури переміщення елемен-
тів, точніше, зміщення елементів масиву праворуч на 1 елемент.
Отже, за допомогою процедури пошуку знайдено номер еле-
мента, який є першим з усіх чисел, що більше заданого числа b
(у випадку, якщо йому немає рівного в масиві).
Для переміщення елементів, починаючи з p-го, праворуч до-
сить організувати таку послідовність:
for i:=n+1 downto k+1 do a[i]:=a[i-1]
Тепер запишемо саму процедуру:

Псевдокод
Procedure insert(int n,int[] a);
Int p, q, s, k, c;
begin
for i:=2 to n do
begin
p:=1; q:=i;
c:=a[i];
while p < q do
begin
s:=(p+q) div 2;
if c > a[s] then
p:=s+1 else q:=s
end;
287
for k:=i downto p+1 do a[k]:=a[k- 1];
a[p]:=c
end
end;

Python C++
def insert(n, a): voidinsert (int n, int[] *a)
for i in range(2, n) : {
p = 1; int p, q, s, k, c;
q = n; for (int i = 2; i < 2; i++)
c = a[i]; {
while p < q : p = 1; q = n;
s = (p + q) % 2; c = a[i];
if c > a[s] : while (p < q)
p = s + 1; {
else: s = (p + q) % 2;
q = s; if (c > a[s])
for k in range(i, p+1, -1) : p = s + 1;
a[k] = a[k – 1]; else
a[p] = c; q = s;
return a }
for (int k = i; k < p+1; i–)
{
a[k] = a[k-1];
a[p] = c;
}
}
}

Сортування масиву простими вставками (продовження).


Розглянувши програму вставлення елемента в упорядкований
масив, не можна не розібрати один з оригінальних способів упо-
рядкування масиву.
Приклад 2.25. Для упорядкування масиву чисел a1, a2, ..., an
можна використовувати алгоритм сортування простими вставка-
ми. Розглянемо такий алгоритм для упорядкування за зростанням.
Переглядати послідовно a2, ..., an і кожен новий елемент a та вста-
вляти на відповідне місце в уже впорядковану сукупність a1, ..., ai-1.
Написати програму, що реалізовує алгоритм.

288
Нехай задано масив чисел (знову для простоти міркувань ві-
зьмемо масив цілих чисел):
123 2510 6 8 3465 44 17
Позначимо його ім'ям a. У нашому прикладі він має 10 елементів.
Уведемо ще один масив із 10 тих самих елементів, але впоря-
дкованих за зростанням. Наше завдання полягатиме в побудові
цього масиву.
За перший елемент упорядкованого масиву b візьмемо пер-
ший елемент масиву a.
Отже, масив b:
12
Тепер беремо другий елемент масиву a – це 3 – і за допомо-
гою процедури швидкого пошуку вставляємо його в упорядко-
ваний масив b. Оскільки 3 менше 12, то він вставиться перед 12,
і впорядкований масив b уже буде складатися із двох елементів:
3 12
Беремо третій елемент масиву a – 25 – і вставляємо в масив b,
одержуємо:
3 12 25
Продовжуватимемо такі дії до тих пір, поки всі n елементів
масиву a не будуть переглянуті. У результаті утворюється новий
упорядкований масив b.
Як усе просто вийшло! Беремо елемент масиву a і вставляємо
в упорядкований масив b, використовуючи процедуру швидкого
пошуку місця елемента в упорядкованому масиві b.
Схема програми:
Program Problem4; {Сортування простими вставками}
опису змінних і масивів;
процедура створення масиву;
процедура швидкого пошуку номера елемента в
упорядкованому масиві;
процедура переміщення елементів у кінець масиву;
begin
виклик процедури створення масиву;
задається перший елемент нового впорядкованого масиву:
b [1]: = a [1];
цикл за кількістю елементів, що залишилися в масиві a,
починаючи від 2:
for i: = 2 to n do
289
begin
звернення до процедури швидкого пошуку номера
елемента в масиві b для елемента масиву a [i];
виклик процедури переміщення елементів у кінець
масиву b;
вставка i-го елемента масиву a на знайдене місце:
b [p]: = a [i]
end;
виведення впорядкованого масиву b на екран
end.

Програма:
Псевдокод
ProgramProblem4; {Сортування простими вставками}
const n := 20;
Int[] t := array[1..n] of Int;
Int[] a := t;
Int[] b := t;
Int: i, p;
Procedure create(Int n; returnInt[] a);
Int i;
begin
writeln('Заданий масив цілих чисел');
for i:=1 to n do
begin
a[i]:=random(201)-100;
write(a[i], ' ')
end;
writeln
end;
Procedure quick_search(Int m, Int c,Int[] b,return Int p);
Int q,s;
begin
p:=1; q:=n;
while p < q do
begin
s:=(p+q) div 2;
if a[s] > b then p:=s+1
else q:=s
end
end;

290
Procedure movement_end(Int i, Int k; return Int[] b);
Int j;
begin
for j:=i downto k+1 do
b[j]:=b[j- 1]
end;
begin
create(n, a);
b[1]:=a[1];
for i:=2 to n do
begin
quick_search(i, a[i], b, p);
movement_end(i, p, b);
b[p]:=a[i]
end;
writeln;
writeln('Масив упорядкований за зростанням');
for i:=1 to n do
write(b[i], ' ');
writeln;
end.

Python C++
n = 20; void quick_search (int[] a, int n, int
t = [0]*n; b, int*p)
a = t; {
b = t; int q, s;
i, p; p = 1;
q = n;
def quick_search(a, n, b, p): while (p < q)
p = 1; {
q = n; s = (p + q) % 2;
while p < q : if (a[s] > b)
s = (p + q) // 2; p = s + 1;
if a[s] > b : else
p = s + 1; q = s;
else: }
q = s; }
return p
void create(int n, int[] *a)
def create(n, a); {
print('Заданий масив цілих cout<< “Заданий масив цілих чи-
чисел'); сел”;

291
for i in range(1, n): for(int i=1; i<n; i++)
a[i]=random.randint(-100, {
100); a[i]=(rand()%201)-100;
print(a[i]); cout<< a[i] << “ “;
print(''); }
return a; cout<<endl;
}
def movement_end(i, k,b);
j = i; void movement_end(int i, int k,int[]
for j in range(j, k+1, -1): *b)
b[j]=b[j-1]; {
create(n, a); for(int j=i; i<k+1; i–)
b[1]=a[1]; b[j]=b[j-1];
for i in range(2, n): create(n, a);
quick_search(i, a[i], b, p); b[1]=a[1];
movement_end(i, p, b); for(i=2; i<n; i++)
{
b[p]=a[i]; quick_search(i, a[i], b, p);
print(''); movement_end(i, p, b);
print('Масив упорядкова- b[p]=a[i];
ний за зростанням'); }
for i in range(1, n): cout<<endl;
print(b[i]); cout<< “Масив упорядкований за
print(''); зростанням”
return b; <<endl;
for(i=1; i<n; i++)
cout<<b[i]<< “”;
cout<<endl;
}

int main ()
{
constintn = 20;
int[] t = new int[n];
int[] a = t;
int[] b = t;
inti, p;
movement_end(i, p ,b);
}

292
Уважно вивчіть програму і спробуйте відповісти на такі запитання:
1) Чому в процедурі швидкого пошуку права межа береться
до m, а не до m + 1 (p: = 1; q: = m;)?
2) Чому при звільненні місця під елемент, що вставляється в
масив b, цикл організовується від i до p + 1 (for k: = i downto p +
1 do b [k]: = b [k-1];)?
3) Чи можна не вводити в процедуру швидкого пошуку но-
мер елемента i (search (i, a [i], b, p)?
Зрозуміло, що недоліком програми є присутність у ній друго-
го масиву b. Чи можна обійтися без нього?
Спробуємо скласти процедуру сортування простими встав-
ками, не використовуючи додатковий масив. Для цього досить у
процедуру search перенести цикл для перегляду елементів маси-
ву a від 2 до n, і такий цикл має бути зовнішнім щодо всіх інших
операторів, які виконуються всередині процедури:
for i: = 2 to n do
begin
На початку циклу встановимо номери елементів нового впо-
рядкованого масиву, а спочатку він складається із одного еле-
мента – першого; значить, ліва межа p: = 1, а права q: = i.
Перший елемент, який візьмемо для вставки в упорядкований
масив, – це елемент під номером 2, тобто a [i]. Його значення
дамо змінній c: = a [i].
Далі починається вже відома вам процедура швидкого пошу-
ку елемента в упорядкованому масиві (який поки складається із
одного елемента):
while p <q do
begin
s: = (p + q) div 2;
if c> a [s] then p: = s + 1 else q: = s
end;
Коли знайдено для цього елемента місце, його треба усунути,
тому всі елементи нового масиву треба зсунути на 1 ліворуч, що
виконується за допомогою циклу:
for k: = i downto p + 1 do a [k]: = a [k- 1];
Нарешті, останній крок – вставити елемент c на звільнене місце:
a [p]: = c.
293
Оформимо програму з процедурою вставки:
Псевдокод
ProgramProblem4; {Сортування простими вставками}
const n := 20;
Int[] t := array[1..n] of Int;
Int[] a := t;
Int: i, p;
Procedure create(Int n; returnInt[] a);
Int i;
begin
writeln('Заданий масив цілих чисел');
for i:=1 to n do
begin
a[i]:=random(201)-100;
write(a[i], ' ')
end;
writeln
end;
Procedure insert(int n,int[] a);
Int p, q, s, k, c;
begin
for i:=2 to n do
begin
p:=1; q:=i;
c:=a[i];
while p < q do
begin
s:=(p+q) div 2;
if c > a[s] then
p:=s+1 else q:=s
end;
for k:=i downto p+1 do a[k]:=a[k- 1];
a[p]:=c
end
end;
begin
create(n, a);
insert(n, a);
for i:=2 to n do
writeln;
writeln('Масив упорядкований за зростанням');
for i:=1 to n do

294
write(a[i], ' ');
writeln;
end.

Python C++
n = 20; voidinsert (int n, int[] *a)
t = [0]*n; {
a = t; int p, q, s, k, c;
i; for (int i = 2; i < 2; i++)
{
def create(n, a); p = 1; q = n;
print('Заданий масив цілих c = a[i];
чисел'); while (p < q)
fori in range(1, n): {
a[i]=random.randint(-100, 100); s = (p + q) % 2;
print(a[i]); if (c > a[s])
print(''); p = s + 1;
return a; else
q = s;
def insert(n, a): }
for i in range(2, n) : for (int k = i; k < p+1; i–)
p = 1; {
q = n; a[k] = a[k-1];
c = a[i]; a[p] = c;
while p < q : }
s = (p + q) % 2; }
if c > a[s] : }
p = s + 1;
else void create(int n, int[] *a)
q = s; {
for k in range(i, p+1, -1) : cout<< “Заданий масив цілих чи-
a[k] = a[k – 1]; сел”;
a[p] = c; for(int i=1; i<n; i++)
return a {
a[i]=(rand()%201)-100;
create(n, a); cout<< a[i] << “ “;
insert(n, a); }
print(''); cout<<endl;
print('Масив упорядкований за }
зростанням');
fori in range(1, n): int main ()
print(a[i]); {

295
print(''); const int n = 20;
int[] t = new int[n];
int[] a = t;
inti;
create(n, a);
insert(n, a);
cout<<endl;
cout<< “Масив упорядкований за
зростанням”
<<endl;
for(i=1; i<n; i++)
cout<<a[i]<< “”;
cout<<endl;
}

Вправи
1. В одновимірному масиві є елементи 0, 1, 2. Переставте їх
на перші три місця за зростанням.
2. Скласти програму визначення задуманого людиною числа
від 1 до 1000 за допомогою 10 запитань. Кожне запитання має
вигляд: "Чи правда, що задумане число більше k?" При цьому
вказується конкретне k. Відповіді людини – це 1 (Так) і 0 (Ні).
Застосувати ідею поділу навпіл ("розділяй і володарюй").
3. Дано два упорядковані масиви a (n) і b (m). Утворити з
їхніх елементів упорядкований масив c (n + m), тобто об'єднати
два упорядковані масиви в один упорядкований масив.
4. Написати програму стиснення масиву відкиданням нульових
елементів. Нулями заповнюється звільнений хвіст масиву.
5. Визначити кількість збіжних елементів двох упорядкованих
одновимірних масивів, створених за допомогою функції випадкових
чисел. Масиви треба створити за допомогою функції випадкових
чисел, упорядкувати за спаданням, а потім порівняти.

Сортування вибором максимального елемента


Приклад 2.26. Знайти максимальний елемент числового масиву.
Математичний аналіз задачі.
Нехай задано деякий числовий масив (знову для простоти мі-
ркувань створимо масив цілих чисел):
32 16 25 47 56 12 87 96 ...

296
Спочатку найбільшим елементом вважатимемо перший, а по-
тім послідовно порівняємо його з іншими елементами масиву.
Як тільки зустрінеться елемент, більший за перший, його вважа-
тимемо найбільшим й продовжимо подальше порівняння, але
вже з новим найбільшим елементом, і так до кінця масиву.
При введенні масиву можна застосувати вже відому проце-
дуру create.
Для визначення найбільшого значення елементів масиву скла-
демо процедуру maximum, користуючись запропонованою блок-
схемою. Її скласти неважко. Початкове значення максимального
елемента нехай дорівнюватиме першому елементу: max:=a [1].
Далі, починаючи з другого, проглядаємо елементи масиву і
порівнюємо з максимальним. Якщо максимальний елемент рап-
том виявляється меншим за який-небудь новий елемент, то но-
вий елемент беремо за максимальний.

Псевдокод
Procedure maximum(n: integer; a: t; var max: integer);
var
i: integer;
begin
max:=a[1];
for i:=2 to n doif max < a[i] then max:=a[i]
end;
С++
void maximum(intn, inta[], int *max)
{
*max = a[0];
for (int i = 1; i <n; i++)
if (*max<a[i])
*max = a[i];
}
Python
def maximum(n, a, max):
max[0] = a[0];
for i inrange(1,n) :
if max[0] <a[i] :
max[0] = a[i]

297
Можуть бути інші варіанти процедур. Наведемо одну з них.
У ній задіяна рекурсія, тому вона досить оригінальна й допома-
гає нам учитися використовувати рекурсивні підпрограми.
Ідею складання рекурсивної процедури визначення найбіль-
шого елемента масиву продемонструємо на прикладі.
Нехай задано масив:
12 34 56 23 85 37 44 15 19 11
12 34 56 23 85 37 44 15 19 11
Процес починається з першого елемента – за найбільший бе-
реться саме він (у нашому випадку – це 12).
Потім береться наступний елемент 34 і порівнюється з найбі-
льшим 12, 34>12; отже, за найбільший береться 34. Далі порів-
нюється 56, 56 >34 тощо.
Однак процес потрібно "пригальмувати", інакше відбудеться
зациклення процедури. Для цього достатньо ввести в початок
процедури умовний оператор. Якщо n=0, то процедуру потрібно
закінчити, тобто виконати перехід у кінець процедури.
Рекурсивна процедура матиме такий вигляд:

Псевдокод
Procedure maximum(n: integer; a: t; var max: integer);
var
i: integer;
begin
max:=a[1];
for i:=2 to n do if max < a[i] then max:=a[i]
end;
С++
void maximum(intn, inta[], int *max)
{
*max = a[0];
for (int i = 1; i <n; i++)
if (*max<a[i])
*max = a[i];
}
Python
def maximum(n, a, max):
max[0] = a[0];
for i in range(1,n) :
if max[0] <a[i] :
max[0] = a[i]
298
Перевага тут не тільки в оригінальності процедури, яка ви-
кликає саму себе, а й у тому, що ми обходимося без традиційних
циклів, передбачених мовами програмування.
Приклад 2.27. Дано масиви a (n), b (k), c (m). Знайдіть зна-
чення найменшого елемента серед цих трьох масивів.
Можливі два способи розв'язання завдання.
Перший спосіб. Знайти найменші елементи кожного із трьох
масивів, а потім, використовуючи вже відомий алгоритм мен-
шого із трьох чисел, знайти найменший елемент.
Другий спосіб. Об'єднати всі три масиви в один d (n + k + m) і
знайти найменший елемент цього нового масиву.
Тепер можна перейти до написання процедури сортування
вибором.
Математичний аналіз завдання. Для сортування масивів мо-
жна використовувати таку просту ідею:
у заданому масиві визначити найбільший елемент і переста-
вити його на останнє місце, а останній елемент поставити на
місце найменшого;
після цього розглянути масив без останнього елемента і ви-
конати ті самі операції пошуку і перестановки тощо, поки не
дійдемо до першого елемента.
Процес можна проілюструвати на частковому прикладі.
Нехай задано масив із восьми елементів:
5, 3, 57, -12, 6, -16, 21, 25.
Визначаємо його найбільший елемент, що дорівнює 57, і пе-
реставляємо з першим елементом. Отримуємо масив:
5, 3, 25, -12, 6, -16, 21, 57.
Тепер розглядаємо масив елементів
5, 3, 25, -12, 6, -16, 21.
У ньому визначаємо найбільший елемент, їм є 25, і перестав-
ляємо із останнім елементом. Масив стане таким:
5, 3, 21,-12, 6,-16, 25.
Продовжуємо до останнього елемента, коли, нарешті, масив
стане впорядкованим за зростанням:
-16,-12, 3, 5, 6, 7, 21, 25.
Схема програми:

299
Програма працює за таким самим принципом, тільки шукає
найменший елемент (замість найбільшого) і переставляє в поча-
ток (замість перестановки в кінець).

Псевдокод
опис;
begin
ввести масив x;
for i:=1 to n do
begin
знайти індекс k найменшого елемента
xi, x(i+1), ..., xn;
переставити xi з xk;
end;
виведення відсортованого масиву;
end.
Деталізація
Пошук індексу k найменшого елемента серед x[i], ..., x[n];
k:=i;
forj:=i+1 tondo
ifx[j] <x[k] thenk:=j;

Перестановка елементів x[i] та x[k] виконується вже відомим


вам способом з використанням додаткової змінної p:
p:=x[i]; x[i]:=x[k]; x[k]:=p
Тепер складаємо процедуру сортування вибором:
Псевдокод
Procedure choice(n: integer; x: t);
var
i, j, k, p: integer;
begin
for i:=1 to n do
begin
k:=i;
for j:=i+1 to n doif x[j] < x[k] then k:=j;
p:=x[i]; x[i]:=x[k]; x[k]:=p
end;
writeln('Упорядкований масив');
300
for i:=1 to n do write(x[i], ' ');
writeln
end;
C++
void choice(intn, intx[])
{
int k, p;
for (int i = 0; i <n; i++)
{
k = i;
for (int j = i + 1; j <n; j++)
if (x[j] <x[k]) k = j;
p = x[i]; x[i] = x[k]; x[k] = p;
}
printf("Упорядкований масив \n");
for (int i = 0; i <n; i++)
printf("%d ", x[i]);
printf("\n");
}
Python
def choice(n, x):
for i in range(n):
k = i;
for j in range(i + 1, n):
if x[j] < x[k] :
k=j
p = x[i]
x[i] = x[k]
x[k] = p;
print("Упорядкований масив ")
for i inrange(n):
print(x)

Приклад 2.28. При розв'язанні практичних задач упорядкуван-


ня xi, ..., xn зазвичай супроводжується деякими додатковими діями.
Наприклад, якщо x1, y1, x2, y2, ..., xn, yn – це значення аргументу x і
деякої функції y = f(x), то перестановка x1, ..., xn має супроводжува-
тись деякою перестановкою y1, ..., yn. Розглянемо цю задачу (пере-
становки значень x1, y1, ..., xn, yn будуть виведені у два рядки).

301
Програма
Псевдокод
Program Problem4;
const
n = 20;
type
t = array[1..n] ofinteger;
var
x, y: t;
p, i, j, k: integer;
begin
writeln('Задані значення аргументу');
randomize;
for i:=1 to n do
begin
x[i]:=random(101);
write(x[i], ' ');
y[i]:=random(101)
end;
writeln;
writeln('Задані значення функції');
for i:=1 to n do write(y[i], ' ');
writeln;
writeln('Упорядковані значення аргументу');
fori:=1 tondo
begin
k:=i;
for j:=i+1 to n doif x[j] < x[k] then k:=j;
p:=x[i]; x[i]:=x[k]; x[k]:=p; write(x[i],' ');
p:=y[i]; y[i]:=y[k]; y[k]:=p
end;
writeln;
writeln('Відповідні значення функцій');
for i:=1 to n do write(y[i], ' ');
writeln
end.
C++
int main()
{
constint n = 20;
int x[n], y[n];
int p, k;

302
printf("Задані значення аргументу\n");
srand(143);
for (int i = 0; i < n; i++)
{
x[i] = rand() % 101;
printf("%3d ",x[i]);
y[i] = rand() % 101;
}
printf("\n");
printf("Задані значення функцій\n");
for (int i = 0; i < n; i++) printf("%3d ", y[i]);
printf("\n");
printf("Упорядковані значення аргументу\n");
for (int i = 0; i < n; i++)
{
k = i;
for(int j = i + 1;j<n;j++)
if(x[j] < x[k])
k = j;
p = x[i]; x[i] = x[k]; x[k] = p; printf("%3d ", x[i]);
p = y[i]; y[i] = y[k]; y[k] = p;
}
printf("\n");
printf("Відповідні значення функції\n");
for (int i = 0; i < n; i++)
printf("%3d ", y[i]);
printf("\n");
return 0;
}
Python
n = 20
x=[0]*n
y=[0]*n
s1=""; s2=""
for i in range(n):
x[i] = randint(0,100); s1+="%3d "%x[i]
y[i] = randint(0,100); s2+="%3d "%y[i]
print("Задані значення аргументу")
print(s1)
print("Задані значення функції")
print(s2)

303
s1, s2 = "", ""
for i in range(n):
k = i;
for j in range(i + 1,n):
if x[j] < x[k] :
k=j
p = x[i]; x[i] = x[k]; x[k] = p; s1+="%3d "%x[i]
p = y[i]; y[i] = y[k]; y[k] = p; s2+="%3d "%y[i]
print("Упорядковані значення аргументу")
print(s1)
print("Відповідні значення функції")
print(s2)

Вправи
1. Скласти програму визначення найменшого елемента маси-
ву не менш ніж двома способами.
2. Дано масив зі 100 цілих чисел. Знайдіть різницю його най-
більшого та найменшого чисел.
3. Дано масиви a(n) та b(m). Чи є серед елементів масиву b(m)
елемент, що дорівнює найбільшому (найменшому) елементу
масиву a(n) (використовувати процедуру швидкого пошуку еле-
мента в упорядкованому масиві).
Написати програму пошуку найбільшого з від'ємних елемен-
тів даного масиву.
4. Знайти найменший з додатних елементів масиву
(x1, x2, ..., x40).
5. Знайти найбільше значення (xi + yi) для масивів (x1, x2, ...,
x40) та ( y1 , y 2 , ..., y 40 ).
6. Для масиву (a1, a2, ..., a80) обчислити найбільше та най-
менше значення модуля різниці між сусідніми елементами.
7. Дано цілі a1, ..., an. З модулів членів даної послідовності вибра-
ти найбільший. Отримати нову послідовність із n цілих чисел (нулів
та одиниць), замінюючи ai нулем, якщо модуль ai не збігається з
обраним значенням, і заміняючи ai одиницею, якщо збігається.
8. Дано дійсні a1, ..., an. Необхідно знайти b, що дорівнює се-
редньому арифметичному чисел a1, ..., an, і найбільше відхилен-
ня від середнього, тобто max(|a1- b|, |a2 - b| , ..., |an - b|).

304
Швидке сортування масиву. Сортувати дані доводиться до-
сить часто, тому виникає необхідність у швидших способах сор-
тування, ніж бульбашкове тощо. Навіть без точних математич-
них міркувань можна зрозуміти, що бульбашкове сортування
дуже тривале. За великої кількості даних, а на практиці так і є,
цим способом користуватися незручно.
Розберемо один з оригінальних способів сортування, який
увійшов в інформатику під назвою швидке сортування. Наведе-
мо основні міркування, що лежать у його основі.
Математичний аналіз завдання. Нехай задано невпорядко-
ваний масив цілих чисел (знову беремо цілі числа для простоти
запису):
45 33 12 18 67 26 15 36 48 89
Назвемо середнім той елемент масиву, ліворуч від якого бу-
дуть числа, менші за нього, а праворуч – більші.
Зрозуміло, що зовсім не обов'язково, щоб середній елемент
був посередині масиву, він іноді може міститися досить далеко
від середини.
Щоб знайти такий елемент, зробимо так. Візьмемо лівий
крайній елемент (45) і будемо порівнювати його з елементами
від правого кінця:
45 33 12 18 67 26 15 36 48 89
Якщо лівий елемент менше правого (45 <89), то все гаразд і
ми знову продовжуємо перегляд із правого кінця:
45 33 12 18 67 26 15 36 48 89
45 менше 48, тому знову йдемо далі до середини, але до се-
редини масиву переходити не будемо:
45 33 12 18 67 26 15 36 48 89
Натрапили на елемент 36, який менше 45. Переставляємо ці
елементи, 45 на місце 36, а 36 – на місце 45 і процес порівняння
з правого кінця припиняємо. Отримуємо такий масив:
36 33 12 18 67 26 15 45 48 89
Починаємо порівняння з лівого кінця масиву. Будемо порів-
нювати елементи ліворуч від 45, починаючи з першого, з елеме-
нтом 45:
36 33 12 18 67 26 15 45 48 89
36 менше 45, тому рухаємося далі праворуч:

305
36 33 12 18 67 26 15 45 48 89
33 менше 45, рухаємося праворуч:
36 33 12 18 67 26 15 45 48 89
12 менше 45, беремо наступний елемент:
36 33 12 18 67 26 15 45 48 89
18 менше 45, порівнюємо наступний елемент:
36 33 12 18 67 26 15 45 48 89
67 більше 45. Умова 67 <45 не виконується, отже, 67 містить-
ся не на своєму місці. Переставляємо його із 45, отримуємо:
36 33 12 18 45 26 15 67 48 89
Після такої перестановки перевіримо номера елементів, які
переставили останніми, дотримуючись вимоги, щоб номер ліво-
го елемента був менше номера правого; як тільки номери ста-
нуть рівними, процес треба припинити.
У нашому випадку 5 < 8, отже, продовжуємо процес, але пере-
гляд елементів починаємо праворуч від елемента 45, ліворуч від 67.
36 33 12 18 45 26 15 67 48 89
45 порівнюємо з 15 за раніше встановленою умовою, щоб лі-
вий елемент був менше правого (45 < 15). Умова не виконуєть-
ся, значить, переставляємо елементи. Отримуємо:
36 33 12 18 15 26 45 67 48 89
Після перестановки елементів починаємо порівняння з іншо-
го кінця масиву, тобто з лівого. Порівнюємо 26 із 45 за умовою
26 < 45. Умова виконується, тому рухаємося далі, залишається
один елемент:
45
Перевіряємо умову 45 < 45. Умова не виконується. Порівню-
ємо праворуч, знову отримуємо, що умова 45 < 45 не виконуєть-
ся. Нарешті, перевіряємо останню умову за номерами елементів.
Номери лівого і правого елементів виявляються рівними, оскі-
льки це той самий елемент (7 = 7). Процес припиняється.
Середнім елементом буде елемент під номером 7, рівний 45.
Масив стане таким:
36 33 12 18 15 26 45 67 48 89
Справді, цей елемент задовольняє визначення середнього
елемента. Ліворуч від нього елементи менше 45, а праворуч –
більше 45.

306
Подальший процес сортування ми не будемо поки розбирати
детально, обмежимося лише ідеєю. Масив поділено середнім еле-
ментом на дві частини – ліворуч від нього і праворуч. До кожної з
них (без середнього елемента) застосуємо той самий процес, що
був описаний вище, тобто в кожній частині знайдемо свій середній
елемент; знову отримаємо дві частини, у яких також знайдемо се-
редні елементи і т. д. У підсумку отримаємо впорядкований масив:
12 15 18 26 33 36 45 48 67 89
Цю частину процесу сортування ми розглянемо пізніше, а за-
раз складемо процедуру визначення середнього елемента за за-
пропонованим алгоритмом.
Приклад 2.29. Скласти програму знаходження середнього
елемента масиву, ліворуч від якого розташовуються елементи
менші від нього, а праворуч – більші (з використанням процедур
перестановки елементів і визначення середнього елемента).
Схема процедури. Назвемо процедуру middle (middle з англ.
– середина).
Масив в основній програмі назвемо іменем x.
Procedure middle (вхідним параметром має бути довжина ма-
сиву k: integer; вихідні параметри – масив і номер середнього
елемента);
var
l, r: integer; {Номера лівого та правого елементів}
begin
Треба присвоїти лівому і правому номерам елементів почат-
кові значення 1 та k (довжина масиву).
Основний цикл має тривати до тих пір, поки номери лівого і
правого індексів не стануть рівними між собою (l = r); тоді треба
організувати зовнішній цикл repeat.
Далі йде цикл перегляду елементів, починаючи з правого кі-
нця; він має тривати, поки лівий елемент буде менше або дорів-
нюватиме правому; водночас номер лівого індексу має бути
менше правого. Маємо цикл:
while (x[l] <= x[r]) and (l < r) do
У циклі треба зменшувати індекс правого елемента, рухаю-
чись до середини r: = r-1;

307
Як тільки знайдеться лівий елемент більше правого, треба
зробити їх обмін, для чого звернутися до процедури exchange
(обмін).
Після обміну треба переглядати елементи ліворуч, для чого
організуємо цикл "поки":
while (x[l] <= x[r]) and (l < r) do.
У циклі треба рухатися зліва направо, переглядаючи елемен-
ти, а для цього збільшувати індекс лівого елемента: l:=l+1.
Якщо знаходимо лівий елемент більше правого, то робимо обмін.
Знову звертаємося до процедури обміну.
Зовнішній цикл закінчується умовою: until l = r;
Складемо процедуру обміну значеннями двох елементів ма-
сиву й деталізуємо процедуру пошуку середнього елемента.

Псевдокод Python
Procedure exchange(int l, int r) def exchange(l, r):
Int p; "Swap"
begin p = x[l];
p:=x[l]; x[l] = x[r];
x[l]:=x[r]; x[r] = p;
x[r]:=p;
end def middle(k,m):
Procedure middle(int k, Array x, "sort"
int m) l = 1;
Int l, r; r = k;
begin while True:
l := 1; r := k; while x[l]<=x[r] and l<r:
Repeat r=r-1;
While((x[l]<=x[r]) and (l<r)) exchange(l,r);#Процедура
do r := r-1; обміну
exchange(l,r);{Процедура while x[l]<=x[r] and l<r:
обміну} l=l+1;
While((x[l]<=x[r]) and (l<r)) exchange(l,r);
do l := l+1; if (l == r):
exchange(l,r); break;
Until l = r; m = l;
m := l;
end

308
С++
int x[] = { 50,44,1,234,45 };
void exchange(intl, intr)
{
int p;
p = x[l];
x[l] = x[r];
x[r] = p;
}
void middle(intk, intm)
{
int l, r;
l = 1;r = k;
do
{
while (x[l] <= x[r] && l<r) {r = r – 1;}
exchange(l, r);//Процедура обміну
while (x[l] <= x[r] && l < r) { l = l + 1; }
exchange(l, r);
} while (l == r);
}

Є деяка особливість у запису вихідних параметрів цієї процедури.


Якщо записати вихідні параметри у вигляді var x: t; m: integer, то
обов'язково отримаємо помилку. У результаті такого запису буде
замість номера m видаватися неіснуючий номер 0, а замість значення
елемента масиву x [m] – неіснуючий елемент. Це відбувається тому,
що, відокремивши вихідні параметри крапкою з комою, ми змушує-
мо комп'ютер сприймати m не як вихідний, а як вхідний параметр.
Щоб уникнути такої помилки, треба ще раз записати службо-
ве слово var і тим самим указати, що m є вихідним параметром.
Тоді запис параметрів процедури буде таким:
Procedure middle(k: integer; var x: t; var m: integer);
У Python тип значення не вказується, тому, щоб передати
щось із методу, укажіть у кінці функції ключове слово return.
Звісно, у жодному підручнику неможливо врахувати всі тон-
кощі та несподіванки, що виникають при програмуванні. Розу-
міння приходить тоді, коли на екрані з'являється повідомлення
про помилку або програма видає неправильний результат і ви
змушені самі шукати джерело помилки. Отже, навчаємося на
своїх помилках.
309
Приклад 2.30. Тепер переробимо програму з прикладу 1 так,
щоб вона виконувала не тільки визначення середнього елемента,
але й за тим самим принципом упорядковувала числовий масив.
Ідея полягає в такому. Перший етап – це знайти середній
елемент усього масиву (знову звернемося до масиву чисел, у
якому визначали середній елемент). Наш середній елемент має
номер 45 і міститься на 7-му місці.
36 33 12 18 15 26 45 67 48 89
Знову використовуємо процедуру визначення середнього елемен-
та, поки для лівої частини масиву, що міститься ліворуч від 7-го но-
мера, тобто від 1-го до 7-го (сам 7-й елемент не входить до розгляду).
36 33 12 18 15 26
Порівнюємо 36 і 26 за умовою 36 < 26. Умова не виконуєть-
ся, тому переставляються елементи 36 і 26. Ця частина масиву
стане такою:
26 33 12 18 15 36
Тут середнім виявився елемент 36, оскільки ліворуч від нього
містяться елементи, менші ніж 36. Продовжуємо процес до час-
тини масиву ліворуч від 36 (весь час рухаємося ліворуч):
26 33 12 18 15 36
Порівнюємо 33 і 15 (33 < 15). Умова не виконується, переста-
вляємо ці елементи, отримуємо:
26 15 12 18 33 36
Знову знайдено середній елемент 33 на 5-му місці. Починає-
мо пошук середнього для 4 елементів масиву з 1-го по 4-й:
26 15 12 18 33 36
26 порівнюється з 18 (26 < 18). Умова не виконується, елеме-
нти переставляються і т. д. Весь час знаходимо середні елементи
лівих частин.
Коли дійшли до першого елемента, процес руху ліворуч при-
пиняється і починається поступовий рух праворуч, тобто визна-
чаються середні елементи для правих частин масиву. У резуль-
таті масив стає впорядкованим.
Весь процес реалізується такою процедурою:
Псевдокод Python
Procedure fast(int q, int p,return x) def fast(q,p,x):
Int s,l,r; "sort method"
begin l = q;
l:=q; r:=p; r = p;
310
s:= x[l]; s = x[l];
Repeat while True:
While ((x[r] >= s) and (l<r)) while(x[r] >= s and l<r):
do r=r-1;
r:=r-1; x[l]=x[r];
x[l]:=x[r]; while(x[l]<=s and l<r):
While ((x[l] <= s) and (l<r)) l=l+1;
do x[r] = x[l];
l:=l+1; if (l == r):
x[r]:=x[l]; break;
Until l = r x[l]=s;
x[l]:=s; if (q<l-1):x = fast(q,l-1,x);
if q < l-1 then fast(q,l-1,x); if (l+1<p):x = fast(l+1, p,x);
if l+1 < p then fast(l+1,p,x); return x;
end

С++
int x[] = { 50,44,1,234,45 };
void fast(intq, intp, intx[])
{
int s, l, r;
s = x[l];
do
{
while ((x[r] >= s) && (l < r)) {r=r-1;}
x[l] = x[r];
while ((x[r] <= s) && (l < r)) {l=l+1;}
x[r] = x[l];
} while (l == r);
x[l] = s;
if (q< l – 1) { fast(q, l – 1, x); }
if (l+1 <p) { fast(l+1, p, x); }
}

Зверніть увагу на дуже важливу особливість цієї процедури:


після виконання її основної частини, тобто визначення серед-
нього елемента, установлюються два умовні оператори.
if q < l- 1 then fast(q, l- 1, x);
if l+1 < p then fast(l+1, p, x)

311
Перший умовний оператор забезпечує рух ліворуч (що ми ро-
збирали на прикладі) і викликає ту саму процедуру – fast, тобто
відбувається її звернення до самої себе. Другий умовний оператор
забезпечує рух праворуч. У ньому також є виклик процедури fast
– виклик самої себе. Отже, ми маємо рекурсивну процедуру.
Приклад 2.31. У масиві є нульові елементи. Скласти програ-
му сортування масиву за зростанням, а потім його стиснення
відкиданням нульових елементів. Нулями заповнюється хвіст
масиву, що звільнився.
Алгоритм
Упорядкування масиву для нас – уже відомий процес. Це
можна зробити за допомогою процедури швидкого сортування,
після чого нульові елементи виявляться на початку масиву. На-
приклад, можливе таке:
0 0 0 0 2 4 6 9 10 12
Тепер треба нульові елементи переставити в кінець масиву.
Отримаємо:
2 4 6 9 10 12 0 0 0 0
Для перестановки нульових елементів у кінець масиву є кіль-
ка способів.
Перший спосіб – підрахувати кількість нульових елементів, а
потім перенумерувати ненульові, починаючи з 1-го і далі за їх-
німи номерами, а решті елементів присвоїти нульові значення.
Другий спосіб – послідовна перестановка нульових елементів
у кінець масиву: беремо перший нульовий елемент і переставля-
ємо в кінець; він виявиться на місці n-го елемента, у нашому
прикладі – на місці десятого:
0 0 0 2 4 6 9 10 12 0
На першому місці знову нульовий елемент. Переставляємо
його, але не на останнє, а на передостаннє місце. Отримуємо:
0 0 2 4 6 9 10 12 0 0
тощо.
Ми скористаємося другим способом, але тепер складемо ре-
курсивну процедуру для перестановки елементів. Звичайно,
можна обійтися без процедури, тим більше рекурсивної, але нам
важливо навчитися використовувати рекурсивні процедури з
масивами.
312
Процедура
С++ Псевдокод
int a[] = { 50,44,1,234,45 }; Procedure swp(int k, int m)
void swp(intk, intm) Int v;
{ Begin
int v; If k = m then goto 1;
if (k == m) { goto stop; } v := a[k];
v = a[k]; a[k] := a[k+1]
a[k] = a[k + 1]; a[k+1] := v;
a[k + 1] = v; swp(k+1,m);
swp(k + 1, m); 1:end
stop:
}
Python
def swp(k,m):
"swap"
while 1:
if(k==m):break;
v = a[k];
a[k] = a[k+1];
a[k+1] = v;
swp(k+1,m);
break;

Складена вона доволі просто. За основу береться перестанов-


ка сусідніх елементів у бік збільшення номера елемента, тобто
переміщення в правий кінець (у хвіст масиву). Це робиться за
допомогою вже відомих нам трьох операторів: v: = a [k]; a [k]: =
a [k + 1]; a [k + 1]: = v;
У цьому полягатиме основна частина процедури, після чого
слід повторити перестановку кілька разів, переставляючи щора-
зу наступні елементи, у яких номер збільшується на одиницю.
Таким чином відбувається переміщення в кінець масиву, але
номер перестановки елементів має збільшуватися на 1, тобто
треба звертатися до тієї самої процедури. Якщо процедуру на-
звати swp, то звернення до неї має бути таким: swp (k + 1, m).
Як зупинити процес, якщо процедура зациклилася?

313
Спочатку слід з'ясувати, для чого потрібна змінна m. Для
цього варто згадати, що перший нульовий елемент переставля-
ється на останнє місце, тоді m має дорівнювати n; другий нульо-
вий елемент має бути переставлений на передостаннє місце,
отже, m має дорівнювати n-1 тощо за кількістю нульових елеме-
нтів. Значення m задається із основної програми, а значення
номера нульового елемента має завжди дорівнювати одиниці,
тому що після кожної перестановки нульового елемента наступ-
ний займає його місце, тобто перебуватиме на місці першого.
Тоді початкове значення k, що задається із основної програми.
дорівнює 1. Щоб процедура не зациклилася на початку, включе-
но умовний оператор, який перевіряє значення k. Якщо воно
дорівнюватиме m, то це буде означати, що нульовий елемент
переставлено і він зайняв своє місце в кінці масиву. Тоді проце-
дуру треба завершити, для чого включено оператор безумовного
переходу goto, що пересилає керування на end.
Для підрахунку кількості нульових елементів на початку впо-
рядкованого масиву в основній програмі є цикл repeat ... ... until, у
якому підраховується кількість нулів і відразу відбувається звер-
нення до процедури перестановки. Однак, оскільки цей цикл за-
кінчується умовою a [d]> 0, то два нульові елементи будуть не
підраховані й не переставлені. Для цього в програму введені ще
два звернення до процедури: swp (1, n- d + 1); swp (1, n- d);
Деталізація програми:
Псевдокод Python
Procedure fast(int q, int p,return x) def swp(k,m):
Int s,l,r; "swap"
begin while 1:
l:=q; r:=p; if(k==m):break;
s:= x[l]; v = a[k];
Repeat a[k] = a[k+1];
While((x[r]>=s) and (l<r)) do a[k+1] = v;
r:=r-1; swp(k+1,m);
x[l]:=x[r]; break;
While((x[l]<=s) and (l<r)) do
l:=l+1; def fast(q,p,x):
x[r]:=x[l]; "sort method"
Until l = r l = q;

314
x[l]:=s; r = p;
if q<l-1 then fast(q,l-1,x); s = x[l];
if l+1<p then fast(l+1,p,x); while True:
end while(x[r] >= s and l<r):
Procedure swp(int k, int m) r=r-1;
Int v; x[l]=x[r];
Begin while(x[l]<=s and l<r):
If k = m then goto 1; l=l+1;
v := a[k]; x[r] = x[l];
a[k] := a[k+1] if (l == r):
a[k+1] := v; break;
swp(k+1,m); x[l]=s;
1:end if (q<l-1):x = fast(q,l-1,x);
Begin if (l+1<p):x = fast(l+1, p,x);
Const n = 5; return x;
For i:=1 to n do
begin if __name__ == '__main__':
write('Уведіть',i,'-й елемент'); n = 5;
readln(a[i]); a = [0]*n;
end for i in range(0,n,1):
fast(1,n,a); a[i] = int(input('Уведіть ' +
d:=1; str(i) + "-й ел. масиву:"));
Repeat a = fast(1,(n-1),a);
swp(1,n-d+1) d=1;
d:=d+1; while True:
Until a[d]>0 swp(1,(n-1)-d+1);
swp(1,n-d+1); d=d+1;
swp(1,n-d); if(a[d]>0):
writeln('Упорядкований масив break;
з нулевими ел. У кінці'); swp(1, (n-1) – d + 1);
for i:=1 to n do write(a[i],' '); swp(1, (n-1) – d);
writeln; print('Упорядкований масив
end з нулевими ел. у кінці');
for i in range(0,n):
print(str(a[i]) + ' ');

С++
void swp(intk, intm, int *a)
{
int v;
if (k == m) { goto stop; }

315
v = a[k];
a[k] = a[k + 1];
a[k + 1] = v;
swp(k + 1, m, a);
stop: cout <<".";
cout << endl;
}
void fast(intq, intp, int *x)
{
int s, l, r,x_r;
l = q; r = p;
s = x[l];
do
{
x_r = x[r];
while ((x_r >= s) && (l < r)) { r = r – 1; }
x[l] = x[r];
x_r = x[r];
while ((x_r <= s) && (l < r)) { l = l + 1; }
x[r] = x[l];
} while (l != r);
x[l] = s;
if (q< l – 1) { fast(q, l – 1, x); }
if (l + 1 <p) { fast(l + 1, p, x); }
}
int main()
{
int n = 5;
int *a = newint[n];
for (int i = 0; i < n; i++)
{
cout <<"a["<< i <<"]:"<< endl;
cin >> a[i];
}
fast(1, n-1, a);
int d = 1;
do
{
swp(1, (n-1) – d + 1,a);
d = d + 1;
} while (a[d] < 0);
swp(1, (n-1) – d + 1,a);

316
swp(1, (n – 1) – d,a);
cout <<"Sorted Array: "<< endl;
for (int i = 0; i < n; i++)
{
cout << a[i] <<" ";
}
cout << endl;
system("pause");
return 0;
}

Приклад 2.32. Скласти програму, яка створює два масиви


чисел за допомогою функції випадкових чисел, упорядковує їх
за допомогою рекурсивної процедури швидкого сортування, а
потім об'єднує в один упорядкований масив також із викорис-
танням рекурсивної процедури.
Створення масивів за допомогою функції випадкових чисел
та їх упорядкування ми вже вивчали, тому не будемо зупинятися
на цих питаннях, а припустимо, що вже маємо два впорядковані
за зростанням масиви, наприклад масив a з 10 елементів і масив
b із 15 елементів.
Масив a: -98-97-77-47-44-24 11 59 75 83
Масив b: -96-75-69-69-68-15-5 11 37 45 47 77 85 92 93
Виникає природна думка порівнювати по черзі елементи маси-
вів, вибирати менший і присвоювати його значення елементу ново-
го масиву. Подивимося, як ця ідея буде реалізовуватися практично:
перший елемент масиву a, a [1] = -98;
перший елемент масиву b, b [1] = -96;
a [1] <b [1] (-98 <-96),
отже, першому елементу нового масиву присвоїти значення
першого елемента масиву a:
c [1]: = a [1], c [1]: = – 98.
Тепер беремо другий елемент масиву a, a[2] = – 97, і порів-
нюємо його з першим елементом масиву b:
a [2] <b [1], (-97 <-96),
отже, другому елементу нового масиву треба присвоїти значен-
ня другого елемента масиву a:
c [2]: = a [2], c [2]: = – 97.

317
Продовжуємо перегляд масиву a, беремо третій елемент, a [3]
= – 77, і порівнюємо з першим елементом масиву b:
a [3] <b [1], (-77 <-96).
Умова не виконується, значить, наступному, третьому елемен-
ту масиву c, присвоюється значення першого елемента масиву b:
c [3]: = b [1], c [3]: = – 96.
Процес триватиме до тих пір, поки не закінчаться елементи
масиву a або b (незважаючи на те, що кількість елементів маси-
ву a менше, ніж кількість елементів масиву b, це не означає, що
перегляд масиву a закінчиться раніше; може статися навпаки,
швидше закінчиться перегляд масиву b, якщо його елементи
будуть менше елементів масиву a).
Однак будь-коли елементи одного з масивів закінчаться. Тоді
треба елементи одного з масивів присвоїти наступним за поряд-
ком елементам масиву c. Як ми бачили, із останнього факту ви-
пливає, що необхідно брати до уваги елементи як масивів a та b,
так і масиву c. Для цього треба мати змінні лічильники й переві-
ряти, чи не скінчилися елементи масивів a та b ще на початку
процедури, перед порівнянням елементів. У іншому випадку мо-
жна спізнитися: лічильник кількості елементів буде збільшено, а
елементи закінчаться; тоді може бути пропущений один з елеме-
нтів іншого масиву, у якого елементи ще не скінчилися. Вихо-
дячи з цих міркувань, можна скласти таку схему процедури.
1. Установити змінним p та q лічильників елементів масивів a
та b початкові значення 0, а лічильнику елементів масиву c –
змінній k – значення 1. (Будемо позначати кількість елементів
масиву a через n, а кількість елементів масиву b – m, тоді кіль-
кість елементів масиву c буде n + m).
2. Перевірити умову if p = n (тобто елементи масиву a скін-
чилися). Якщо вона виконується, то треба збільшити значення
лічильника елементів іншого масиву на 1 і присвоїти відповід-
ному елементу масиву c значення елемента масиву b: q: = q + 1;
c[k]: = b[q]; потім продовжити процес перегляду елементів ма-
сивів. Повністю рядок програми буде таким:
if p = n then
begin
q:=q+1;
c[k]:=b[q];
goto ... end;
318
3. Перевірити умову if q = m (тобто елементи масиву b скін-
чилися). Якщо вона виконується, то збільшити лічильник еле-
ментів масиву a на 1 і присвоїти відповідному елементу масиву
c значення елемента масиву a:
p:=p+1; c[k]:=a[p].
Після цього продовжити перегляд елементів масиву:
if q = m then
begin
p:=p+1;
c[k]:=a[p];
goto ... end;
4. Записати оператори порівняння елементів масивів і при-
своювання елементам масиву c потрібних значень:
if a[p+1] < b[q+1]
then begin p:=p+1; c[k]:=a[p]; goto ... end
else begin q:=q+1; c[k]:=b[q]; goto ... end;
5. Оскільки процедура рекурсивна, то треба записати звер-
нення до цієї ж процедури.
Щоб процедура не зациклилася, перед початком виконання
операторів процедури треба включити умовний оператор, який
має перевіряти значення лічильника k (якщо k = n + m + 1, то
процедуру закінчити). Зверніть увагу, що порівняння лічильника
виконується не за допомогою n + m, а з використанням n + m +
1. Це викликано тим, що початкове значення k дорівнює 1, а
останнє виконання процедури має бути при k = n + m. Зрозумі-
ло, вона має закінчитися, коли k дорівнюватиме n+m+1.
Деталізація
Псевдокод Python
Procedure new(int n, int m, int q, def new(n,m,q,p,k, c):
int p, int k, return Array c) if(k==n+m+1): goto .1
Begin if(p==n):
If k=n+m+1 then goto 1; q=q+1;
If p=n then c[k]=b[q];
begin goto .2;
q:=q+1; if(q==m):
c[k]:=b[q]; p=p+1;

319
goto 2; c[k] = a[p];
end goto .2;
if q=m then if a[p+1]<b[q+1]
begin p=p+1;
p:=p+1; c[k] = a[p];
c[k]:=a[p]; goto .2;
goto 2; else:
end q=q+1;
if a[p+1]<b[q+1]then c[k]=b[q];
begin label .2
p:=p+1; new(n,m,q,p,k+1,c);
c[k]:=a[p]; label .1
goto 2; print("end of program");
end
else
begin
q:=q+1;
c[k]:=b[q];
end
2: new(n,m,q,p,k+1,c);
1: end

С++
void New(intn, intm, intq, intp, intk, int *c)
{
if (k == n + m + 1) { goto one; }
if (p == n)
{
q = q + 1;
c[k] = b[q];
goto two;
}
if (q == m)
{
p = p + 1;
c[k] = a[p];
goto two;
}
if (a[p + 1] < b[q + 1])
{

320
p = p + 1;
c[k] = a[p];
goto two;
}
else
{
q = q + 1;
c[k] = b[q];
}
two: New(n, m, q, p, k + 1, c);
one: cout <<"end of program"<< endl;
}

Початкові значення q, p та k вводяться із основної програми.


Програма
Псевдокод Python
Program Problem5 n = 5;
Const n=10,m=15 m = 10;
Int array[1..n] a; a = [0]*n;
Int array[1..m] b; b = [0]*m;
Int array[1..n+m] c; c = [0]*(n+m);
Int I,p,q;
Procedure fast(int q, int p,return x) @with_goto
Int s,l,r; def new(n,m,q,p,k, c):
begin if(k==n+m+1): goto .one
l:=q; r:=p; if(p==n):
s:= a[l]; q=q+1;
Repeat c[k]=b[q];
While((a[r]>=s) and (l<r)) do goto .two;
r:=r-1; if(q==m):
a[l]:=a[r]; p=p+1;
While((a[l]<=s) and (l<r)) do c[k] = a[p];
l:=l+1; goto .two;
a[r]:=a[l]; if(a[p+1]<b[q+1]):
Until l = r p=p+1;
a[l]:=s; c[k] = a[p];
if q<l-1 then fast(q,l-1,a); goto .two;
if l+1<p then fast(l+1,p,a); else:
end q=q+1;
321
Procedure fast1(int q,int p,return x) c[k]=b[q];
Int s,l,r; label .two
begin new(n,m,q,p,k+1,c);
l:=q; r:=p; label .one
s:= b[l];
Repeat
While((b[r]>=s) and (l<r)) do def fast(q,p,x):
r:=r-1; "sort method"
b[l]:=b[r]; l = q;
While((a[l]<=s) and (l<r)) do r = p;
l:=l+1; s = x[l];
b[r]:=b[l]; while True:
Until l = r while(x[r] >= s and l<r):
b[l]:=s; r=r-1;
if q<l-1 then fast(q,l-1,b); x[l]=x[r];
if l+1<p then fast(l+1,p,a); while(x[l]<=s and l<r):
end l=l+1;
Procedure new(int n, int m, int q, x[r] = x[l];
int p, int k, return Array c) if (l == r):
Begin break;
If k=n+m+1 then goto 1; x[l]=s;
If p=n then if (q<l-1):x = fast(q,l-1,x);
begin if (l+1<p):x = fast(l+1, p,x);
q:=q+1; return x;
c[k]:=b[q];
goto 2; def fast1(q,p,x):
end "sort1 method"
if q=m then l = q;
begin r = p;
p:=p+1; s = x[l];
c[k]:=a[p]; while True:
goto 2; while(x[r] >= s and l<r):
end r=r-1;
if a[p+1]<b[q+1]then x[l]=x[r];
begin while(x[l]<=s and l<r):
p:=p+1; l=l+1;
c[k]:=a[p]; x[r] = x[l];
goto 2; if (l == r):
end break;
else x[l]=s;
begin if (q<l-1):fast1(q,l-1,x);

322
q:=q+1; if (l+1<p):fast1(l+1, p,x);
c[k]:=b[q];
end
2: new(n,m,q,p,k+1,c);
1: end if __name__ == '__main__':
Begin for i in range(0,n,1):
Randomize a[i] = random.randint(10,
For i:= 1 to n do 100);
a[i]:= random(201)-100; for i in range(0,m,1):
For i:= 1 to m do b[i] = random.randint(10,
b[i]:= random(201)-100; 100);
fast(1,n,a); fast(1,n-1,a);
writeln('Задано впорядкований print('1 Array :a');
1-й масив'); for i in range(0, n, 1):
For i:=1 to n do write(a[i],''); print(a[i]);
Writeln; print('');
fast1(1,m,b); fast1(1,m-1,b);
writeln('Задано впорядкований print('2 Array :b');
2-й масив'); for i in range(0, m, 1):
For i:=1 to n do write(b[i],''); print(b[i]);
Writeln; print('');
new(n,m,0,0,1,c); new(n-1,m-1,0,0,1,c);
writeln('Новий упорядкований print('3 Array :c');
об'єднаний масив'); for i in range(0, (n-1)+(m-1), 1):
For i:=1 to n+m do write(c[i],' '); print(c[i]);
Writeln; print('');
End

С++
void New(intn, intm, intq, intp, intk, int *c, int *a, int *b)
{
if (k == n + m + 1) { goto one; }
if (p == n)
{
q = q + 1;
c[k] = b[q];
goto two;
}
if (q == m)
{
323
p = p + 1;
c[k] = a[p];
goto two;
}
if (a[p + 1] <b[q + 1])
{
p = p + 1;
c[k] = a[p];
goto two;
}
else
{
q = q + 1;
c[k] = b[q];
}
two: New(n, m, q, p, k + 1, c,a,b);
one: cout <<".";
}

int n = 5;
int m = 10;
int *a = newint[n];
int *b = newint[m];
int *c = newint[n + m];

void fast(intq, intp)


{
int l = q, r = p;

int piv = a[l + (r – l) / 2];


while (l <= r)
{
while (a[l] < piv)
l++;
while (a[r] > piv)
r–;
if (l <= r)
swap(a[l++], a[r–]);
}
if (q< r)

324
fast(q, r);
if (p> l)
fast(l, p);
}
void fast1(intq, intp)
{
int l = q, r = p;
int piv = b[l + (r – l) / 2];
while (l <= r)
{
while (b[l] < piv)
l++;
while (b[r] > piv)
r–;
if (l <= r)
swap(b[l++], b[r–]);
}
if (q< r)
fast(q, r);
if (p> l)
fast(l, p);
}

int main()
{
int num;

for (int i = 0; i < n; i++)


{
num = 10 + rand() % 100;
a[i] = num;
}
for (int i = 0; i < m; i++)
{
num = 10 + rand() % 100;
b[i] = num;
}

fast(0, n – 1);
cout <<"1 Array a:"<< endl;

325
for (int i = 0; i < n; i++)
{
cout << a[i] <<' ';
}
cout << endl;
fast1(0, m – 1);
cout <<"2 Array b:"<< endl;
for (int i = 0; i < m; i++)
{
cout << b[i] <<' ';
}
cout << endl;
New(n-1, m-1, 0, 0, 0, c,a,b);
cout <<"3 Array c:"<< endl;
for (int i = 0; i < m+n; i++)
{
cout << c[i] <<' ';
}
cout << endl;
system("pause");
}

Можливий також інший варіант процедури об'єднання, пов'я-


заний з використанням циклу "поки" (while ... do ...). Процедура
може бути побудована за таким принципом.
Спочатку перевіряється, як і раніше, значення k. Коли воно
стає рівним n + m + 1, робота процедури закінчується .
Далі організовується цикл "поки". Поки p < n та q < m, треба
порівнювати значення елементів масивів a та b.
Якщо a[p + 1] <b[q + 1], то збільшуємо p на одиницю, а потім
присвоюємо елементу c[k] значення елемента масиву a[p]; після
цього переходимо знову на виклик процедури new (goto 2). Ін-
акше треба збільшувати q на одиницю (q: = q + 1) і присвоювати
елементу масиву c[k] значення елемента масиву b[q].
Після закінчення циклу "поки" треба виконати роботу умов-
них операторів, які в попередній процедурі не надто природно
виконувалися спочатку:
if p = n then begin q:=q+1; c[k]:=b[q]; goto 2 end;
if q = m then begin p:=p+1; c[k]:=a[p] end;

326
Процедура
Псевдокод Python
Procedure new(int n, int m, int q, int @with_goto
p, int k, return Array c) def new(n,m,q,p,k, c):
begin if(k==n+m+1): goto .one;
if (k=n+m+1) then goto 1; while((p<n) and (q < m)):
while (p<n)and(q<m) do if(a[p+1]<b[q+1]):
begin p=p+1;
if(a[p+1]<b[q+1] then c[k]=a[p];
begin goto .two;
p:=p+1; else:
c[k]:=a[p] q=q+1;
goto 2; c[k]=b[q];
end goto .two;
else if(p==n):
begin q=q+1;
q:=q+1; c[k]=b[q];
c[k]:=b[q]; goto .two;
goto 2; if(q==m):
end p=p+1;
end c[k]=a[p];
if(p=n)then label .two
begin new(n,m,q,p,k+1,c);
q:=q+1;c[k]:=b[q];goto 2; label .one
end
if(q=m)then
begin
p:=p+1;c[k]:=a[p]
end
2:new(n,m,q,p,k+1,c);
1:end
С++
void New(intn, intm, intq, intp, intk, int *c)
{
if (k == n + m + 1) { goto one; }
while ((p<n)&&(q<m))
{
if (a[p + 1] < b[q + 1])
{
p = p + 1;
c[k] = a[p];
goto two;

327
}
else
{
q = q + 1;
c[k] = b[q];
goto two;
}
}
if (p == n) {
q = q + 1;
c[k] = b[q];
goto two;
}
if(q==m)
{
p = p + 1;
c[k] = a[p];
}
two:New(n, m, q, p, k + 1, c);
one:cout <<"EOF"<< endl;
}

Вправа для самостійної роботи


Цілочисловий масив упорядкувати за зменшенням: a1 > a2>
...> an і знайти сукупність таких елементів ai, ..., ak, сума яких
дорівнює заданому числу m. Якщо такої сукупності не знайдено,
то видати відповідне повідомлення (використовуйте за можли-
вості рекурсивні процедури).

2.9.5. Деякі задачі з масивами чисел

Не можна не зупинитись на ще одній групі цікавих задач із маси-


вами чисел, деякі з яких розглянуто в книжці Абрамова та Зіма [15] .
Приклад 2.33. Дано цілі числа a1, a2, ..., an, де n – деяка конс-
танта. Потрібно отримати всі різні числа, що входять у послідо-
вність a1, ..., an (іншими словами, з кожної групи рівних чисел
треба вивести тільки одне: якщо дано числа 1, 0, 1, 2, 2, 0, 3, то
мають бути виведені 1, 0, 2, 3).

328
Алгоритм
Алгоритм розв'язання дуже простий. Береться перший еле-
мент масиву і порівнюється з попереднім (якщо він дорівнює
попередньому, то порівняння продовжується далі; якщо не дорі-
внює, то виводиться на екран), а оскільки попереднього немає,
то й рівного йому немає, а це означає, що він виводиться на ек-
ран як той, що не має рівних.
Далі береться другий елемент і таким саме чином порівню-
ється з попередніми. Якщо він дорівнює першому елементу, то
береться третій елемент і порівнюється з першим; якщо йому
немає рівного, то він виводиться на екран. Якщо рівні йому є, то
береться наступний елемент, четвертий, і почергово порівню-
ється з першим, другим, третім. Як тільки знаходиться йому
рівний, процес його порівняння з іншими елементами припиня-
ється й береться наступний, п'ятий елемент для порівняння з
першим, другим, третім, четвертим. Якщо знаходиться хоча б
один рівний, то береться наступний елемент – шостий і т. д.
Порівняння елементів можна виконувати одразу після їх уве-
дення з клавіатури. Наприклад:

Псевдокод
Program Problem1;
Int n = 20;
Int array[1..n] t;
Int array[1..n] a;
Int I, j, k;
begin
for i:=1 to n do
begin
write('Enter ', i, ' element '); readln(a[i]);
for j:=1 to i- 1 do if a[i] = a[j] then goto 1;
writeln(a[i]);
1: end
end.

Python C++
N = 10 int main()
flag = False {
array = [] const int N = 10;

329
for i in range(0, 9): int *array = new int[N];
array.append(int(input("Enter bool flag = false;
element: "))) for (int i = 0; i<N; i++){
for j in range(0,i): cout << "\nEnter " << i + 1 << "
if(array[i] == array[j]): value: ";
flag = True cin >> array[i];
if(flag != True):
print(array[i]) for (int j = 0; j<i; j++){
flag = False if (array[i] == array[j])
flag = true;
}
if (!flag)
cout << array[i];

flag = false;
}
return 0;
}

Можна зменшити кількість порівнянь вигляду a[i] = a[j], як-


що введене число заноситься до масиву a[1], a[2], ... , тільки у
випадку відсутності для нього рівного серед наявних елементів
масиву. Напишемо програму, що реалізує цей підхід. Змінна k
буде показувати, скільки різних чисел уже виведено.
Приклад:

Псевдокод
Program Problem1a;
Int n = 20;
Int array[1..n]t;
Int array[1..n] a;
Int I, j, k;
begin
k:=0;
for i:=1 to n do
begin
write('Enter ', i, ' element '); readln(x);
for j:=1 to k doif x = a[j] then goto 1;
writeln(x);

330
k:=k+1;
a[k]:=x;
1: end;
writeln(k)
end.

Python C++
k=0 int main()
N = 10 {
flag = False int k = 0;
array = [] const int N = 10;
for i in range(0, 9): int *array = new int[N];
x = int(input("Enter element: ")) bool flag = false;
for j in range(0,k): int x = 0;
if(x == array[j]):
flag = True for (int i = 0; i<N; i++)
if(flag != True): {
print(x) cout << "\nEnter " << i + 1 << "
k=k+1 value: ";
array.append(x) cin >> x;
flag = False for (int j = 0; j<=k; j++)
print(k) {
if (x == array[j])

flag = true;
}
if (!flag)
{
cout << x;
k++;
array[k] = x;
}
flag = false;
cout << "\n" << k;
}
return 0;
}

331
Бачимо, що значення, яких набувають змінні k та i, на кож-
ному етапі виконання програми зв'язані нерівністю k ≤ i . Якщо
до закінчення програми виконано k < n, то це означає, що змінні
a[k+1], a[k+2], ..., a[n] не отримали в ході виконання програми
ніяких значень.
Іноді в задачах, схожих на останню, корисно розглянути до-
датковий масив b[1], b[2], ...; число b[i] може, наприклад, пока-
зувати, скільки разів серед вихідних даних зустрічається зна-
чення, що дорівнює a[i].
Приклад 2.34. Нехай є масив цілих чисел a1, ..., an, де n – деяка
константа, і нехай потрібно вивести кожне число один раз, указавши
при цьому, скільки разів воно зустрічається серед a1, ..., an.
Будемо враховувати розв'язання попередньої задачі, але при
цьому розглянемо додатковий масив b[1], ..., b[n]. Маємо таку
схему:
ProgramProblem2;
опис;
begin
k:=0;
fori:=1 tondo
begin
введення елементів масиву
якщо x збігається з деяким a[j], 1 <= j <= k,
то збільшити b[j] на 1 і перейти до мітки 1;
k:=k+1; a[k]:=x; b[k]:=1;
1: end;
вивести a[1], b[1], a[2], b[2], ..., a[k], b[k]
end.
Програма
Псевдокод
Program Problem2;
Int n = 20;
Int array[1..n]t;
Int array[1..n] a;
Int array[1..n] b
Int i, j, k, x;
begin
332
k:=0;
for i:=1 to n do
begin
write('Enter', i, 'element '); readln(x);
for j:=1 to k do
if x = a[j] then
begin
b[j]:=b[j]+1;
goto 1
end;
k:=k+1;
a[k]:=x;
b[k]:=1;
1: end;
for i:=1 to k do writeln(a[i], ' ', b[i])
end.

Python C++

k=0 int main()


N = 10 {
flag = False const int N = 10;
array = [] int *array = new int[N];
array2 = [] int *array2 = new int[N];
for i in range(0, 10): bool flag = false;
x = int(input("Enter element: int x = 0;
")) for (int i = 0; i<N; i++){
for j in range(0,k): cout << "\nEnter " << i + 1 << "
if(x == array[j]): value: ";
flag = True cin >> x;
array2[j] = array2[j] + 1
if(flag != True): for (int j = 0; j<=k; j++)
array.append(x) {
array2.append(1) if (x == array[j])
k=k+1 {
flag = False flag = true;
for i in range(0,k): array2[j] = array2[j] + 1;
print(str(array[i]) + ": " + }
str(array2[i])) }
if (!flag)
333
{
array[k] = x;
array2[k] = 1;
}
flag = false;
}
for(int i = 0; i < k; i++) {
cout << "\n" << array[i] << ": " <<
array2[i];
}
return 0;
}

Можна окремо написати процедуру, за допомогою якої еле-


менти масиву виводяться один раз, навіть якщо вони повторю-
ються, а також виводиться кількість їхніх повторень.

Псевдокод
Procedure repetition(Int n; Array a, Array b; return Int k)
Int i, j;
begin
k:=0;
for i:=1 to n do
begin
for j:=1 to k do
if a[i] = a[j] then
begin
b[j]:=b[j]+1;
goto 1
end;
k:=k+1;
a[k]:=a[i];
b[k]:=1;
1: end;
end;

Python C++
def repetition(n, a, b, k): void Repetition(int n, int* a, int* b,
k=0 int& k)
flag = False {
for i in range(0,n): k = 0;

334
for j in range(0,k): bool flag = false;
if(a[i] == a[j]): for (int i = 0; i < n; i++)
b[j] = b[j] + 1 {
flag = True for (int j = 0; j <= k + 1; j++)
if(flag != True): {
k=k+1 if (a[i] == a[j])
a[k] = a[i] {
b[k] = 1 b[j] = b[j] + 1;flag = true;
return k }
if (!flag)
{
a[k] = a[i];b[k] = 1;k++;
}
}
flag = false;
}
}

2.9.6. Решето Ератосфена

Згадаємо, які числа називаються простими. Це натуральні чи-


сла, більші за 1, які діляться тільки на 1 і на себе.
Із простими числами пов'язана не тільки велика кількість завдань
у математиці, а також їхнє величезне практичне використання. Для
простоти їх знаходження були складені таблиці простих чисел.
Одним із цікавих древніх методів знаходження простих чисел
є метод, запропонований грецьким математиком Ератосфеном
(275–194 рр. до н. е.). Він написав на папірусі, натягнутому на
рамку, усі числа від 1 до 1000 і проколював складні числа. Папі-
рус став як решето, яке "просіює" складні числа, а прості зали-
шає. Звідси назва методу – решето Ератосфена.
Подивимося, як це виглядає на папері. Нехай написані всі на-
туральні числа від 2 до n:
2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, ...
Перше число – 2 – є простим. Починаємо закреслювати (у
нашому запису – підкреслювати) усі числа, які діляться на 2,
тобто всі парні числа, а значить, кожне друге число:
2, 3, 4 , 5, 6 , 7, 8 , 9, 10 , 11, 12 , 13, ...
− − − − −

335
Далі беремо наступне по порядку не підкреслене число і під-
креслюємо всі числа, кратні йому (що діляться на нього), і т. д.
Таким чином, ми підкреслимо всі складні числа, а прості зали-
шаться не підкресленими:
2, 3, 4 , 5, 6 , 7, 8 , 9 , 10 , 11, 12 , 13, ...
− − − − − −
За цим принципом складемо програму, тільки замість підкре-
слення будемо заміняти складні числа нулями.
Математичний аналіз розв'язання та алгоритм. Ідея розв'я-
зання зрозуміла із історії її виникнення, але одразу постають
деякі запитання.
1. Нехай у нас n чисел. За принципом Ератосфена береться
число 2 і закреслюються парні числа; далі береться 3 і закрес-
люються кратні трьом; незакресленим лишається 5 (4 закресле-
но, оскільки воно парне). Закреслюються всі кратні 5 тощо.
До якого ж граничного числа треба брати ненульові числа?
Якщо всього чисел 100, то треба перевіряти числа до 100 або до
97 (це останнє просте число між 2 та 100)?
Щоб відповісти на це запитання, згадаємо, що дільники будь-
якого цілого числа m, якщо такі взагалі є, містяться у проміжку
від 2 до кореня квадратного з числа m , не враховуючи 1 і
саме число. Звідси нескладно зробити висновок, що якщо задані
числа до 100, то достатньо брати для перевірки числа з проміж-
ку від 2 до 100, тобто до 10, адже найбільше число в цій послі-
довності 100, а значить, навіть найбільший дільник, якщо він
існує, може бути 10.
Це дає нам можливість обмежити цикл перевірки чисел від 2
до sqrt(n), до кореня квадратного з n.
2. А якщо корінь квадратний із числа n не добувається наці-
ло? Наприклад, для 200? Відповідь зрозуміла! Треба округлити
наближене значення кореня до найближчого цілого числа, мен-
шого від sqrt (n). У даному випадку виходить 14,142135 .... Тре-
ба округлити до 14. Тут виникає інша проблема, адже sqrt (n)
має тип дійсний (real), а якщо організовано цикл for, то за гра-
ничне значення для змінної циклу ми можемо взяти тільки ціле
число, дійсні числа в цьому циклі неприпустимі.

336
Ви вже знаєте, що для виконання такої операції в Pythonі є
вбудована функція trunc, яку ми застосуємо для складання про-
грами. Отже, треба організувати цикл for від 2 до trunc (sqrt (n))
(зважте, наскільки менше вже стало роботи для комп'ютера!).
Що ж виконувати в циклі? Знову звернемося до прикладу:

2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, ...

Номера елементів масиву:


2 3 4 5 6 7 8 9 10 11 12 13 ...
Взято для перевірки число 2 (воно ж є другим елементом за-
даного масиву). Помножимо його на 2, отримаємо 4 і почнемо
перевірку з четвертого елемента. Якщо четвертий елемент ді-
литься на 2, то замінити його на 0. Далі до номера 4 додамо зна-
чення взятого елемента 2, отримаємо номер 6. Перевіряємо шос-
тий елемент, чи ділиться він на 2, і т. д.
Після перевірки ділення на 2 беремо 3, множимо на 2, отри-
муємо 6. Тепер треба перевірити шостий елемент, але він дорів-
нює нулю, тобто вже викреслений зі списку, що перевіряється.
Отже, збільшуємо номер 6 на величину взятого елемента – на 3,
отримуємо 9. Перевіряємо елемент під номером 9. Таким є чис-
ло 9, воно ділиться на 3, значить, замінюємо його на 0, тобто
викреслюємо з числа перевірених і т. д. Тепер перевірку треба
робити до кінця елементів масиву, тобто до n.
Таким чином, для перевірки на ділення беруться числа від 2 до
trunc(sqrt(n)), а діляться на них числа до n-го включно. Для вико-
нання цієї частини програми треба організувати цикл "while".
Коли така перевірка закінчена і серед елементів заданого ма-
сиву з'явилося багато нульових (складних чисел значно більше,
ніж простих), треба позбутися нульових елементів і створити
новий масив, у якому будуть тільки прості числа. Звичайно,
можна було б цього не робити, а відразу виводити на екран про-
сті числа, але ми мислимо про подальше. Адже може виникнути
завдання, де треба буде використовувати тільки прості числа з

337
якогось проміжку, тоді нам не потрібні нульові елементи, а го-
товий масив простих чисел знадобиться. Отже, відкидаємо ну-
льові елементи та створюємо новий масив тільки із простих чи-
сел. Залишається вивести його на екран, що дуже легко зробити!
Виходить алгоритм. Можливі інші варіанти, якщо у вас з'яв-
ились ідеї, то реалізуйте їх, а ми продовжимо складання схеми
програми, дотримуючись наведених вище міркувань.
Схема програми:
Program Problem3;
опис: константа, що дорівнює кількості чисел;
два масиви однакового розміру;
інші змінні;
begin
цикл, що створює масив натуральних чисел від 1 до n;
змінній f присвоюється значення, що дорівнює
f:=trunc(sqrt(n));
цикл від 2 до f: for i:=2 to f do
у циклі, якщо a[i] <> 0, то змінній b присвоїти значення цьо-
го елемента й організувати цикл "while ", у якому перевірити
ділення ненульових елементів масиву на b;
якщо елемент ненульовий і ділиться на b, то присвоїти йому
значення 0, збільшити значення змінної циклу на величину b і
продовжити цикл; інакше, якщо елемент дорівнює нулю, то
збільшити значення змінної циклу на b і продовжити цикл до n;
закінчити цикл "while";
закінчити цикл for;
змінній-лічильнику простих чисел присвоїти початкове зна-
чення 0; організувати цикл for, у якому елементам нового маси-
ву присвоїти значення ненульових елементів, тобто простих
чисел;
вивести на екран масив простих чисел і кількість простих
чисел;
end.
Деталізація. Деталізацію почнемо не з опису змінних, а зі
складання програми. Опис змінних і масивів виконаємо потім.
1. Цикл, що створює масив цілих чисел від 1 до n:
for i:=1 to n do a[i]:=i;
338
2. Установити змінній f граничне значення:
f:=trunc(sqrt(n));
3. Цикл від 2 до f: for i:=2 to f do
у циклі, якщо a[i] <> 0, тоді змінній b присвоїти значення
цього елемента і організувати цикл "while", у якому перевірити
ділення ненульових елементів масиву на b;
for i:=2 to f do
begin
if a[i] <> 0 then
begin
b:=a[i];
j:=b*2;
while j <=n do
begin
Зазначимо, що в програмі можна легко обійтись без змінної
b, а зробити так: j:=a[i]*2. Змінну b уведено для зручності.
4. Якщо елемент ненульовий і ділиться на b, то присвоїти
йому значення 0, збільшити значення змінної циклу на величину
b і продовжити цикл; інакше, якщо елемент дорівнює нулю, то
збільшити значення змінної циклу на b і продовжити цикл до n;
закінчити цикл "while";
begin
if a[j] mod b = 0 then a[j]:=0;
j:=j+b
end
else
j:=j+b
end
Закінчити цикл for; end
6. Змінній-лічильнику простих чисел присвоїти початкове
значення 0;
організувати цикл for, у якому елементам нового масиву
присвоїти значення ненульових елементів, тобто простих чисел;
k:=0;
for i:=2 to n do
if a[i] <> 0 then
339
begin
k:=k+1;
c[k]:=a[i]
end;
7. Вивести на екран масив простих чисел і кількість простих
чисел;
writeln('Прості числа з проміжку [2, ', n, ']');
for i:=1 to k do write(c[i], ' ');
writeln;
writeln('Усього простих чисел із цього проміжку ', k)
8. Тепер можна виконати опис змінних і масивів:
const
n = 100;
type
t = array[1..n] ofinteger;
var
a, c: t;
i, j, b, f, k: integer;
Можна скласти окрему процедуру "Решето Ератосфена", що
дасть можливість використовувати її в інших програмах:
Procedure eratosfen(n: integer; a: t; var c: t; var k: integer);
var
f, i, j, b: integer;
begin
f:=trunc(sqrt(n));
for i:=2 to f do
begin
if a[i] <>0 then
begin
b:=a[i];
j:=b*2;
while j <= n do
begin
if a[j] <> 0 then
begin
if a[j] mod b = 0 then a[j]:=0;
340
j:=j+b
end else j:=j+b
end
end
end;
k:=0;
for i:=2 to n do
if a[i] <> 0 then
begin
k:=k+1;
c[k]:=a[i]
end;
end;

Приклад 2.35. Гольдбах висловив припущення, що кожне


парне число, що більше або дорівнює 4, може бути зображене у
вигляді суми двох простих чисел. Це припущення досі не дове-
дено і не спростовано. Написати програму перевірки цієї гіпоте-
зи для даного парного числа.
Математичний аналіз задачі. Скористаємося прикладом,
щоб, проаналізувавши його, скласти алгоритм розв'язання.
Нехай уведено парне число 14. За допомогою попередньої
програми "Решето Ератосфена" можна легко знайти всі прості
числа від 2 до 14. Ними будуть:
2 3 5 7 11 13
Більше того, за допомогою тієї самої програми можна легко
дізнатися кількість простих чисел. У даному випадку їх буде 6.
Подальший процес очевидний. Беремо перше просте число і
складаємо його з усіма попередніми або рівним йому простими
числами. Для простого числа 2 таким попереднім або рівним
простим числом буде тільки число 2. Перевіряємо: 2+2 = 14,
умова не виконується. Беремо наступне просте число із заданого
масиву, число 3. Для нього попередніми або рівним є вже два
числа: 2 і 3. Перевіряємо: 3 + 2 = 14, умова не виконується; 3 + 3
= 14, умова не виконується. Третє просте число – 5. Йому пере-
дують або дорівнюють уже три простих числа: 2, 3 і 5. Перевіря-
ємо кожне з них у сумі з 5: 2 + 5 = 14, умова не виконується; 3 +
5 = 14, умова не виконується; 5 + 5 = 14, умова не виконується.

341
Четверте просте число 7, йому передують або дорівнюють
чотири числа: 2, 3, 5, 7. Перевіряємо кожне з них у сумі із 7.
2 + 7 = 14, умова не виконується; 3 + 7 = 14, умова не виконується;
5 + 7 = 14, умова не виконується; 7+7 = 14, умова виконується.
Отже, ці прості числа потрібно вивести на екран.
Можна було б цим обмежитися, якщо б перед нами стояло
завдання просто з'ясувати, чи можна певне число зобразити у
вигляді суми двох простих чисел. Ми можемо відповісти впев-
нено і закінчити процес. Однак ми хочемо знати, чи є ще спосо-
би зображення парного числа у вигляді суми двох простих чи-
сел. Тому процес перевірки варто продовжити. Беремо наступне
просте число – 11. Для нього попередніми є: 2, 3, 5, 7, 11.
Перевіряємо: 2+11 = 14, умова не виконується; 3+11= 14,
умова виконується, отже, і цей варіант зображення числа 14 у
вигляді суми двох простих чисел варто вивести на екран і про-
довжити перевірку.
5+11 = 14, умова не виконується; 7+11 = 14, умова не вико-
нується; 11 + 11 = 14, умова не виконується.
Наступне просте число 13. Перевіряємо його в сумі з попере-
дніми або рівним йому.
2+13 = 14, умова не виконується; 3+13 = 14, умова не викону-
ється; 5+13 = 14, умова не виконується; 7+13 = 14, умова не ви-
конується; 11+13=14, умова не виконується; 13+13 = 14, умова
не виконується.
Усі прості числа перевірені, перевірку потрібно закінчити.
Розв'язання задачі знайдено: 14 може бути зображено у вигляді
суми двох простих чисел. Знайдено також способи зображення
цього числа, їх два: 7+7 та 3+11.
Чому ж для перевірки обраного простого числа беруться
тільки менші або рівне йому прості числа? Адже можна було б
перевірити обране просте число, складаючи його з усіма прос-
тими числами, меншими за задане парне число, тобто з усіма
можливими простими числами для даного випадку. Однак, якщо
піти таким шляхом, то неминучі повторення. Перевірте!
Тепер можна скласти алгоритм розв'язання, тобто написати під-
програму-процедуру, що визначає прості числа за методом Ерато-
сфена, вхідними параметрами якої будуть: задане парне число, до
якого треба знаходити прості числа від 2 до n, де n – задане парне
число; другим параметром є числовий масив від 1 до n.
342
Вихідні параметри: числовий масив c – тільки прості числа;
k – кількість елементів масиву.
В основній програмі, після введення користувачем числа і ви-
клику процедури простих чисел, потрібно організувати цикл за
кількістю елементів масиву простих чисел від 1 до k. Усередині
циклу зробити ще один цикл із параметром j від 1 до i (де i –
змінна зовнішнього циклу), у якому перевіряти суму простих
чисел c[i]+c[j] і порівнювати її із заданим парним числом. Якщо
сума дорівнює йому, то відповідну інформацію вивести на екран.
Схема програми:
Program Problem4;
опис змінних і процедури визначення простих чисел;
begin
write(Введіть парне число, що більше або дорів-
нює 4, ');
write('Менше або дорівнює ', n); readln(m);
eratosfen(m, a, c, k);
write('Число ', m, ' можна зобразити у вигляді суми ');
writeln('двох простих чисел ');
for i:=1 to k do
for j:=1 to i do
if c[i]+c[j] = m then writeln(c[i], '+', c[j]);
end.
Після необхідної деталізації отримаємо:
Псевдокод
Program Problem4;
Int n = 1000;
Int array a[1..n];
Int array c[1..n];
Int i, j, k;
Procedure eratosfen(Int n,Array a,Array c,Int k);
Int f, i, j, b;
begin
f:=trunc(sqrt(n));
for i:=2 to f do
begin
if a[i] <> 0 then
343
begin
b:=a[i];
j:=b*2;
while j <= n do
begin
if a[j] <> 0 then
begin
if a[j] mod b = 0
then a[j]:=0;
j:=j+b
end
else
j:=j+b
end
end
end;
k:=0;
for i:=2 to n do
if a[i] <> 0 then
begin
k:=k+1;
c[k]:=a[i]
end;
end;
begin
write('Enter number more or equal 4, ');
write('but less than ', n, ' '); readln(m);
for i:=1 to n do a[i]:=i;
eratosfen(m, a, c, k);
write('Number', m, ' is representable as sum of');
writeln('two prime numbers ');
for i:=1 to k do
for j:=1 to i do
if c[j]+c[i] = m then writeln(c[j], '+', c[i])
end.

Python C++
import math void eratosfen(int n, int* a, int* c,
int& k){
def eratosfen(n, a, c, k): int f, b, j, i;
344
f = math.trunc(math.sqrt(n)) f = trunc(sqrt(n));
for i in range(1,f): for(i = 1; i < f; i++)
if(a[i] != 0): {
b = a[i] if(a[i] != 0)
j=b*2 {
while(j <= n): b = a[i];
if(a[j – 1] != 0): j = b * 2;
if(a[j – 1] % b == 0): while(j <= n)
a[j – 1] = 0 {
j=j+b if(a[j – 1] != 0)
else: {
j=j+b if (a[j – 1] % b == 0)
k=0 a[j – 1] = 0;
for i in range (1,n): j = j + b;
if(a[i] != 0): }
c.append(a[i]) else
k=k+1 {
return k; j = j + b;
}
N = 1000 }
array = [] }
array2 = [] }
m=0 k = 0;
k=0 for(i = 1; i < n; i++)
m = int(input("Enter number more {
or equal 4 but less 1000: ")) if (a[i] != 0)
for i in range(1,N): {
array.append(i) c[k] = a[i];
k = eratosfen(m, array, array2, k) k++;
print("Number is representable as a }
sum of 2 prime numbers") }
for i in range(0,k): }
for j in range(0, i + 1):
if(array2[j] + array2[i] == m): int main()
print(str(array2[j]) + " " + {
str(array2[i])) const int N = 1000;
int* array = new int[N];
int* array2 = new int[N];
int m, i = 0;
int k = 0;

345
cout << "Enter number more or
equal 4 but less than 1000: ";
cin >> m;
for(i = 0; i < N; i++){
array[i] = i + 1;
}

eratosfen(m, array, array2, k );


cout << "Number " << m << " is
representable as a sum of 2 prime
numbers";
for(i = 0; i < k; i++) {
for(int j = 0; j <= i; j++) {
if(array2[j] + array2[i] == m) {
cout << endl << array2[j] << " "
<< array2[i];
}
}
}
return 0;
}

2.9.7. Схема Горнера для розрахунку


полінома n-го степеня

Ми вже знаємо, що для розрахунку полінома n-го степеня


y = a1xn + a2 xn−1 + ... + an x + an+1 зручно використовувати формулу
Горнера y = (...(( a1 x + a2 ) x + a3 ) x + ... + an ) x + an +1.
Якщо вираз усередині дужок позначити як yi, то значення вира-
зу в наступних дужках можна обчислити, використовуючи рекуре-
нтну формулу yi +1 = yi x + ai +1. Значення полінома y з'являється
після повторення процесу в циклі n разів. Початкове значення y1
доцільно взяти рівним a1, а цикл починати з i = 2. Усі коефіцієнти
полінома і вільний член зазвичай зводяться в масив, що складаєть-
ся із n + 1 елементів (n – порядок полінома). Якщо поліном не міс-
тить членів з деякими степенями x, то на відповідному місці в ма-
сиві необхідно помістити коефіцієнт, що дорівнює 0.
346
Приклад 2.36. Розрахувати значення багаточлена
y = 2x8 − x6 + 4 x5 − 5x2 + 6 x + 1,
використовуючи формулу Горнера. Коефіцієнти полінома зруч-
но зобразити масивом (2; 0; -1; 4; 0; 0; -5; 6; 1). Порядок поліно-
ма n = 8.
Складемо функцію обчислення значення полінома за схемою
Горнера:
Function gorner(x: real; a: t):real;
var
i: integer;
begin
y:=a[1];
for i:=2 to n+1 do y:=y*x+a[i];
gorner:=y
end;
Не втрачайте можливості використати рекурсію, хоча б для
тренування, тим більше що нам не раз доведеться використову-
вати її у своїх програмах.
Спочатку дамо рекурсивне визначення схемі Гонера:
Якщо степінь дорівнює 0 то gorner: = a[0]
Інакше gorner: = gorner(k-1,x,a)*x+a[k]
З використанням рекурсивної процедури програма матиме
вигляд:
Псевдокод
Real array[0..n] t;
Function real gorner(Int k; Real x; Array a)
begin
if k = 1 then gorner:=a[1]
else
begin
gorner:=gorner(k-1,x,a)*x+a[k];
end;
end;

Python C++
def gorner(k, x, a): double gorner(int k, double* a) {
if(k == 1): if(k = 1)
gorner = a[1] gorner = a[1];

347
else: else
gorner = gorner(k-1,x,a) * x + a[k] gorner = gorner(k-1,x,a) * x + a[k];
}

Вправи
1. Скласти програму, після виконання якої з'ясується, чи є се-
ред a1, a2, ..., an хоча б одна пара збіжних за величиною елемен-
тів.
2. Написати програми, після виконання яких у масиві a1, a2,
..., an визначається кількість сусідств:
а) двох додатних чисел;
б) двох чисел різного знака;
в) двох чисел одного знака, причому абсолютна величина
першого числа має бути більше за друге число.
3. Два непарні прості числа, що відрізняються на два, нази-
вають близнюками. Близнюками є, наприклад, числа 5 і 7, 11 і
13, 17 і 19 тощо. На початку натурального ряду такі числа зу-
стрічаються досить часто, але що більше ми просуваємося в
область великих чисел, то менше й менше їх стає. Відомо, що в
першій сотні є 8 близнюків, далі вони розміщені дуже нерівно-
мірно й ми знаходимо їх усе рідше, набагато рідше, ніж прості
числа. Досі неясно, чи кінцева кількість близнюків, до того ж ще
не знайдено способу, за допомогою якого можна було б розв'я-
зати цю проблему. Напишіть програму, яка буде знаходити всі
числа-близнюки в інтервалі [2; 1000]. Використовуйте для зна-
ходження простих чисел процедуру "Решето Ератосфена".
4. Складіть функцію, яка буде перевіряти, чи є числа, що міс-
тяться по обидва боки від заданого парного числа, близнюками.
5. При розв'язанні завдань Гольдбаха і Виноградова були
складені програми, що перевіряють їх правильність для одного
парного або непарного числа. Змініть і доповніть програми так,
щоб вони розв'язували ці питання для всіх парних або непарних
чисел із заданого проміжку.
6. Розрахувати значення багаточлена
z = 2x12 − 4,5x10 + x9 + 3x7 − 0,5x4 − x2 +1,
використовуючи формулу Горнера.

348
7. Розрахувати значення полінома
z = x 8 + 2 x 7 + 3 x 6 + 4 x 5 + 5 x 4 + 6 x 3 + 7 x 2 + 8 x + 9.
Вказівка. Оскільки коефіцієнти полінома – числа натурально-
го ряду, то зводити їх у масив не має сенсу. Обчислення їх доці-
льно виконувати в процесі розв'язання. Тоді формула для обчис-
лення поточного значення полінома матиме вигляд
z n = z n −1 ⋅ x + n.
8. Розрахувати значення s = (1 + x)8 , використовуючи форму-
лу Горнера:
1   2  3  
s =   ... ⋅ x + 1 ⋅ ⋅ x + 1 x + ... + 1 ⋅ 8 ⋅ x + 1.
8   7  6  
x2 x12
9. Розрахувати суму членів ряду z = 1 + x + + ... + , вико-
2! 12!
ристовуючи формулу Горнера:
  x  x  x  
z =    ...  + 1  ⋅ + 1  ⋅ + ... + 1  ⋅ x + ... + 1  ⋅ x + 1.
   12  11  10 
  

Вправи (з використанням двовимірних масивів)


1. Написати програму побудови цілочислової матриці,
елементи якої визначаються рівностями:
aij = i + 2 j; i = 1, 2, ..., n; j = 1, 2, ..., m.
2. Складіть програму підрахунку суми елементів у кожному
рядку й кожному стовпці двовимірного масиву. Кількість рядків
і стовпців довільна. Масив задається за допомогою функції
випадкових чисел.
3. Уводиться матриця a(m, n) з 0 та 1. Знайти в ній квадратну
підматрицю з одних одиниць максимального розміру.
4. У двовимірному масиві знайдіть номер елемента, що
дорівнює заданому числу D.
5. Уводиться матриця a(m, n) з 0 та 1. Знайти в ній
прямокутну підматрицю з одних одиниць максимального
розміру (тобто з максимальним добутком висоти на довжину).

349
6. Дано масив A[N, M]. Знайти максимальну суму елементів
прямокутного підмасиву за всіма можливими прямокутними
підмасивами.
7. Складіть матрицю A(m, n) так, щоб елементи її більшої
діагоналі дорівнювали нулю.
8. Дано матрицю
 b11 b12 
 
 b21 b22  .
 ... ... 
 
 b91 b92 
Написати програму побудови матриці з тими самими
розмірами, елементи i-го рядка якої дорівнюють bi1 + bi 2 та
bi 2 × bi 2 , відповідно.
9. Складіть програму обчислення добутків елементів такої
матриці, розташованих по двох основних діагоналях:
4 6 7 3 4
 
12 17 9 6 8 
 3 1 4 9 10  .
 
 6 5 6 11 7 
 4 9 5 8 17 
 

10. Дано матрицю

 a11 ... a1n 


 
 ... ... ...  .
a 
 m1 ... amn 
Написати програму побудови векторів b1, b2, ..., bm, елементи
яких дорівнюють: а) сумам елементів рядків; б) добуткам
елементів рядків; в) найменшим елементам рядків; г) значенням
середніх арифметичних елементів рядків.

11. Написати програми виконання таких завдань: у матриці

350
 a11 a12 ... a1n 
 
 a21 a22 ... a2 n 
 ... ... ... ... 
 
 an1 an 2 ... ann 
знайти:
а) суму всіх елементів;
б) суму діагональних елементів (елементів з рівними
значеннями обох індексів);
в) суму позадіагональних елементів;
г) значення найбільшого й найменшого з діагональних
елементів.
12. Написати програму, у результаті виконання якої
з'ясується, які з трійок a1i, a2i, a3i (i = 1, 2, ..., 17) можуть служити
сторонами трикутника. Нехай буде виведений масив b1, b2, ...,
b17, що складається з нулів і одиниць. Якщо bi = 1, то a1i, a2i, a3i
можуть бути сторонами трикутника; якщо bi = 0, то ні.
13. Дано цілі числа n, m і вектори a1, a2, ..., am; b1, b2, ...,bn.
Побудувати матрицю, елементи якої визначаються aj рівностями
aj
cij = , де і = 1, 2, ..., n; j = 1, 2, ..., m.
1− | bi |
14. Дано натуральне число n. Написати програму побудови
матриці порядку n:
1 0 ... 0 
 
0 1 ... 0 
 ... ... ... ... 
 
0 0 0 1
(на діагоналі – одиниці, поза діагоналлю – нулі).
15. Дано натуральне число n. Написати програму побудови
матриці порядку n:
 n 0 ... 0 
 
 n −1 n ... 0 
 ... ... ... ... 
 
 1 2 ... n 
(над діагоналлю стоять нулі).
351
16. Дано масив a1, a2, a3, ..., an. Написати програму побудови
матриці порядку n:
 a1 a2 a3 ... an − 2 an −1 an 
 
 a2 a3 a4 ... an −1 an a1 
 ... ... ... ... ... ... ... 
 .
 an − 2 an −1 an ... an −5 an − 4 an −3 
a an a1 ... an − 4 an −3 an − 2 
 n −1 
 an a1 a2 ... an −3 an − 2 an −1 
17. Сформувати з масиву a[1], a[2], ..., a[p] квадратну
матрицю порядку n. Відомо, що p = n × n . Елементи a[1],
a[2], ..., a[n] мають утворювати перший рядок, елементи a[n+1],
a[n+2], ..., a[2 × n] – другий рядок і т. д. Скласти програму.

2.9.8. Множини

Множина – це сукупність однотипних неіндексованих об'єктів.


Множинним називається тип, значеннями якого є множини
значень, включаючи нуль, обрані з деякого іншого типу, що є
базовим для множинного типу. Запишемо, наприклад, тип, який
містить числа від 0 до 22:

Псевдокод
Type number=set of 0..22;
С++
set<int> mySet;
for (int i = 0; i <= 22; i++)
mySet.insert(i);
Python
a = set(range(23))

Для опису множин використовують:

Псевдокод
SET OF тип

352
С++
set<> тип
Python
set() функція, що повертає тип

Тут тип – базовий для множини, тобто тип елементів множи-


ни, за який можна брати будь-який порядковий тип, окрім тих,
які не дозволяють використовувати в даному тексті конкретні
мови. Наприклад:

Псевдокод
Type T1=(jan, feb, mar, apr, jun, jul, aug, sep, oct, nov, dec);
Month= set of T1;
Var m: Month;
Mm: T1;
С++
typedefenum {Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov,
Dec} Months;
set<Months> m;
Months Mm;
Python
Mm = Enum('Month', 'Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov
Dec')
m = set(Months)
#або
classMonth(Enum):

Jan=1;Feb=2;Mar=3;Apr=4;May=5;Jun=6;Jul=7;Aug=8;Sep=9;Oct=10;
Nov=11;Dec=12
m = set(Months)

У цьому оголошенні змінних зазначено, що значенням змін-


ної m може бути будь-яка сукупність назв місяців, а значенням
Mm – тільки одна з назв. Присвоєння сукупності назв викону-
ється таким чином:
Псевдокод
m:=[feb, mar, apr, jun];
m:=[feb .. jun];
m:=[];

353
С++
m = { Feb, Mar, Apr, Jun };
m = {};
Python
m = {Month.Feb, Month.Mar, Month.Apr, Month.Jun}
m = {}

Операції над множинами. До змінних типу "Множина" за-


стосовні такі операції:
множині можна присвоїти множину того самого типу;
операція об'єднання +:
C++ Python
c.insert(a.begin(), a.end()); a.union(b,..)
c.insert(b.begin(), b.end()); a|b|..
or
vector<type> a, b, c;
set<type> c2;

set_union(a.begin(), a.end(), b.begin(),
b.end(), back_inserter(c));
c2.insert(c.begin(), c.end());

операція різниці:
C++ Python
vector<type> a, b, c; a.difference(b,..)
set<type> c2; a-b-..

set_difference(a.begin(), a.end(),
b.begin(), b.end(), back_inserter(c));
c2.insert(c.begin(), c.end());

операція перетину *:
C++ Python
vector<type> a, b, c; a.intersection(b,..)
set<type> c2; a&b&..

set_intersection(a.begin(), a.end(),
b.begin(), b.end(), back_inserter(c));
c2.insert(c.begin(), c.end());

354
операція еквівалентності =:
C++ Python
a==b a==b
a.isdisjoint(b)

операція нееквівалентності <>:


C++ Python
a!=b a!=b
!a.isdisjoint(b)

операція перевірки, чи є ліва множина підмножиною правої <=:


C++ Python
vector<type> a, b, c; a.issubset(b)
set<type> a2, b2, c2; a<=b

a2.insert(a.begin(), a.end());
b2.insert(b.begin(), b.end());
set_intersection(a.begin(),
a.end(), b.begin(), b.end(),
back_inserter(c));
c2.insert(c.begin(), c.end());
a2==c2

операція перевірки, чи є права множина підмножиною лівої >=:


C++ Python
vector<type> a, b, c; a.issuperset(b)
set<type> a2, b2, c2; a>=b

a2.insert(a.begin(), a.end());
b2.insert(b.begin(), b.end());
set_intersection(a.begin(),
a.end(), b.begin(), b.end(),
back_inserter(c));
c2.insert(c.begin(), c.end());
b2==c2

операція включення in; її результатом є true, якщо даний еле-


мент належить до множини, і false – у іншому випадку:

355
C++ Python
a.find(el) !=a.end() elin a

Наприклад, у межах попереднього опису


Псевдокод
Feb in m
С++
m.find(Feb) != m.end()
Python
Month.Feb in m

дає результат true, а


Псевдокод
Dec in m
С++
m.find(Dec) != m.end()
Python
Month.Dec in m

– результат false.
Псевдокод
[feb,dec]<=(m+dec)
С++
vector<Months> a, b, c, f, result;
set<Months> result2, c2, f2;
f = { Feb, Dec };
a = { Feb, Mar, Apr };
b = { Dec };
f2.insert(f.begin(), f.end());
set_union(a.begin(), a.end(), b.begin(), b.end(),
back_inserter(c));
c2.insert(c.begin(), c.end());
set_intersection(c.begin(), c.end(), f.begin(), f.end(),
back_inserter(result));
result2.insert(result.begin(), result.end());
f2 == result2
Python
print({Month.Feb,Month.Dec}<=(m|set({Month.Dec})))

356
Результат – true, тому що всі елементи множини, яка містить-
ся ліворуч, є підмножиною множини, що міститься праворуч.

Розглянемо ще приклад визначення і задання множин:


Псевдокод
Type digitChar = Set Of '0' .. '9';
digit = Set Of 0 .. 9;
var s1, s2, s3: digitChar;
s4, s5, s6: digit;
begin
s1:=['1','2','3'];
s2:=['3','2','1'];
s3:=['2','3'];
s4:=[0..3,6];
s5:=[4,5];
s6:=[3..9];

C++
set<char> s1, s2, s3;
set<int> s4, s5, s6;
s1 = { '1','2','3' };
s2 = { '3','2','1' };
s3 = { '2', '3' };
for (int i = 0; i <= 3; i++)
s4.insert(i);
s4.insert(6);
s5 = { 4, 5 };
for (int i = 3; i <= 9; i++)
s6.insert(i);
Python
s1 = set('123')
s2 = set('321')
s3 = set('23')
s4 = set(range(0, 4))|set(range(6,7))
s5 = { 4, 5 };
s6 = set(range(3,10));

Для порівняння в таблиці наведені операції над множинами


для різних мов програмування:

357
Мова Операція Семантика Приклад Результат
Псевдо * s1*s3 ['2', '3']
Перетин
С++ відсутня –| |– –| |–
множин
Python & s1&s3 {'2','3'}
Псевдо + s4+s5 [0. .6]
Об'єднання
С++ відсутня –| |– –| |–
множин
Python | s4|s5 {0,..,6}
Псевдо - s2-s3 ['1']
Різниця
С++ відсутня –| |– –| |–
множин
Python - s2-s3 {'1'}
s1=s2 True
Псевдо =
s1=s3 False
True,
якщо множини s1==s2 1
С++ ==
s1==s3 0
еквівалентні
s1==s2 True
Python ==
s1==s3 False
s1<>s2 False
Псевдо <>
s1<>s3 True
True,
s1!=s2
С++ != якщо множини
не еквівалентні s1!=s3
s1!=s2
Python !=
s1!=s3
Псевдо <= True, якщо s5<=s6 True
С++ відсутня друга множина –| |– –| |–
Python <= містить першу s5<=s6 True
Псевдо >= True, якщо s2>=s3 True
С++ відсутня перша множина –| |– –| |–
Python >= містить другу s2>=s3 True
Псевдо In Перевірка 7-5 In s4 True
приналежності
відсутня, по-
С++ значення виразу, –| |– –| |–
яснення нижче
який стоїть ліво-
відсутня, по- руч, множині, яка
Python –| |– –| |–
яснення нижче стоїть праворуч

У С ++ є функція find(), яка дозволяє дізнатися, чи є елемент


(не множина) у даній множині.
Аналогічно в Python є функція in, яка також дозволяє дізна-
тися, чи є елемент (не множина) у даній множині.
358
Вправи
1. Дано три множини: Х1, Х2, Х3, що містять цілі числа з ді-
апазону 1…100. Відомо, що потужність кожної з них становить
10. Сформувати нову множину Y = (X1UX2) ∩ (X2 \ X3), з якої
виділити підмножину непарних чисел. На екран вивести почат-
кові дані та отримані множини. Значення елементів початкових
множин увести з клавіатури.
2. Дано три множини: Х1, Х2, Х3, що містять цілі числа з ді-
апазону 1…100. Відомо, що потужність кожної з них становить
10. Сформувати нову множину Y = (X1UX2) \ (X2∩X3) і вивести
на екран її потужність. Перевірити, чи є у множині Y числа, що
діляться на 6 без залишку. Значення елементів початкових мно-
жин увести з клавіатури.
3. Дано дві множини, М та N, що складаються із 10 цілих чи-
сел з діапазону 1…100. Виділити з них підмножини М1 чисел,
що діляться на 3 без залишку, і N1 чисел, що діляться на 2 без
залишку. На друк вивести потужність і значення елементів мно-
жини MN = M1 N1.
4. Дано три множини: X1, X2, X3, що містять цілі числа з ді-
апазону 100 ... 200. Відомо, що потужність кожної з них стано-
вить 10. Сформувати нову множину Y = (X1∩X2) ∩ (X1∩X3).
На друк вивести множини X1, X2, X3 та Y.
5. Дано три множини: X1 = {1,2,3, ..., 20}, X2 = {10,20,30, ...,
90} та X3 = {1,3,5, ..., 19,21}. Сформувати множину Y = (X1 \ X2)
∩ (X1 \ X3) ∩ (X2 \ X3), з якої виділити підмножину Y1 чисел,
що діляться на 4 без залишку. На друк вивести множину Y і
потужність множини Y1. Початкові дані множини ввести з кла-
віатури.
6. У східному календарі роки мають назви тварин: пацюка,
бика, тигра, зайця, дракона, змії, коня, вівці, мавпи, півня, соба-
ки, свині. Крім того, через кожні два роки змінюється колір у
такому порядку: синій, червоний, жовтий, білий, чорний. На-
приклад, 1992 – рік чорної мавпи, 1993 – чорного півня, 1994 –
синього собаки тощо. Написати програму, яка переводить зада-
ний рік у його назву за східним календарем (використовуйте тип
"Перерахування").

359
7. Дано три множини: X1 = {1,2,3, ..., 20}, X2 = {10,20, ...,
190,200} та X3 = {10,11,12, ... 40}. Сформувати множину Y = (X2
X3) \ ((X1∩X2) \ (X1∩X3)) і множину Y1, що складається з еле-
ментів Y, поділених на 2. Якщо отримане в результаті поділу
число не ціле, то округлити його до найближчого цілого. На
друк вивести Y та Y1. Початкові множини ввести з клавіатури.
8. Дано три множини: X1 = {T2, T4, T6, T8, T10}, X2 = {T1,
T2, T3, T4, T5} та X3 = {T2, T3, T5, T7, T8}. Сформувати мно-
жину Y = (X2 \ X3) ∩ (X1 \ X3). На друк вивести Y та її потуж-
ність. Початкові множини описати як типізовані константи.
9. Розробити програму для визначення, яким алфавітом (ла-
тиницею чи кирилицею) уведено з клавіатури символ. На друк
вивести введений символ з коментарем, наприклад: Символ "А"
набраний кирилицею.

360
ЛІТЕРАТУРА
1. Доусон М. Программируем на Python / М. Доусон. – СПб. :
Питер, 2014.
2. Лутц М. Изучаем Python / М. Лутц. – 4-е изд. ; пер. с англ.
– СПб. : Символ-Плюс, 2011.
3. Лутц М. Программирование на Python / М. Лутц. – 4-е
изд. ; пер. с англ. – СПб. : Символ-Плюс, 2011. – Т. I
4. Лутц М. Программирование на Python / М. Лутц. – 4-е
изд. ; пер. с англ. – СПб. : Символ-Плюс, 2011. – Т. II
5. Керниган Б. В. Язык программирования С / Б. В. Керниган,
Д. М. Ричи. – 2-е изд. – М. : Изд. дом "Вильямс", 2009.
6. Компиляторы. Принципы, технологии, инструментарий
/ А. В. Ахо, М. С. Лам, Р. Сети, Д. Д. Ульман. – 2-е изд. – М. :
Изд. дом "Вильямс", 2008.
7. Вирт Н. Алгоритмы + структуры данных = программы
/ Н. Вирт. – М. : Мир, 1985.
8. Дал У. Структурное программирование / У. Дал,
Е. Дейкстра, К. Хоор. – М. : Мир, 1975.
9. Візуалізатор online на Python [Електронний ресурс]. – Режим
доступу : http : // pythontutor . com / visualize . html # mode = edit
10. Завдання на Python на основі рейтингу [Електронний ре-
сурс]. – Режим доступу : https://www.hackerrank.com
11. Федоров Д. Ю. Программирование на языке высокого
уровня Python : учеб. пособ. для прикладного бакалавриата
/ Д. Ю. Федоров. – М. : Изд-во Юрайт, 2018. – (Серия : Бакалавр.
Прикладной курс).
12. Свейгарт Е. Учим Python, делая крутые игры / Е. Свей-
гарт. – Пер. с англ. – М. : Ексмо, 2018.
13. Свейгарт Ел. Автоматизация рутинных задач с помощью
Python. Практическое руководство для начинающих / Е. Свей-
гарт. – Пер. с англ. – М. : ООО "И. Д. Вильямс", 2017.
14. Метиз Е. Изучаем Python. Программирование игр, визуали-
зация данных, веб-приложения / Е. Метиз. – СПб. : Питер, 2017.
15. Абрамов С. А. Начало программирования на языке Пас-
каль / С. А. Абрамов, Е. В. Зима. – М. : Наука. Гл. ред. физ.-мат.
лит., 1987.
361
ЗМІСТ

Передмова ......................................................................................... 3
Розділ 1. Вступ до програмування мовою Python ......................... 4
1.1. Особливості мови Python..................................................... 4
1.2. Дзен Python ........................................................................... 6
1.3. Основи синтаксису Python................................................... 7
1.3.1. Коментарі ...................................................................... 7
1.3.2. Літеральні константи ................................................... 8
1.3.3. Числа ............................................................................. 8
1.3.4. Рядки.............................................................................. 9
1.3.5. Змінні........................................................................... 11
1.3.6. Об'єкти......................................................................... 11
1.3.7. Логічні та фізичні рядки ............................................ 15
1.3.8. Відступи ...................................................................... 16
1.4. Оператори та вирази .......................................................... 19
1.4.1. Оператори ................................................................... 19
1.4.2. Порядок обчислення .................................................. 20
1.4.3. Зміна порядку обчислення......................................... 21
1.4.4. Асоціативність............................................................ 21
1.5. Керувальні структури ........................................................ 23
1.5.1. Логічні операції та операції порівняння .................. 23
1.5.2. Логічні оператори....................................................... 24
1.5.3. Виконання за умовою та порожнеча ........................ 25
1.5.4. Альтернативні гілки
програми (Chained conditionals) ............................... 26
1.5.5. Порожні блоки............................................................ 27
1.5.6. Вкладені умовні оператори
(Nested conditionals) ................................................... 28
1.6. Цикли. Оператор циклу while ........................................... 31
1.6.1. Лічильники.................................................................. 32
1.6.2. Нескінченні цикли...................................................... 33
1.6.3. Альтернативна гілка циклу while.............................. 34
1.6.4. Табулювання функцій................................................ 35
362
1.6.5. Вкладені оператори циклу
і двовимірні таблиці .................................................. 37
1.6.6. Анонімні функції (функція lambda) ......................... 38
1.6.7. Функція генератора.................................................... 39
1.7. Цикли. Оператор циклу for................................................ 41
1.7.1. Оператор break............................................................ 42
1.7.2. Оператор continue....................................................... 43
1.8. Визначення та використання
функцій і модулів ................................................................. 45
1.8.1. Функції ........................................................................ 45
1.8.2. Параметри функцій .................................................... 47
1.8.3. Локальні змінні........................................................... 48
1.8.4. Зарезервоване слово "global"..................................... 49
1.8.5. Зарезервоване слово "nonlocal"................................. 50
1.8.6. Значення аргументів за умовчанням ........................ 51
1.8.7. Ключові аргументи .................................................... 52
1.8.8. Змінна кількість параметрів ...................................... 53
1.8.9. Декоратори.................................................................. 54
1.8.10. Оператор return ......................................................... 58
1.8.11. Рядки документації .................................................. 59
1.8.12. Особливості використання
функцій у Python....................................................... 60
1.8.13. Рекурсивні функції................................................... 62
1.9. Використання модулів ....................................................... 64
1.9.1. Файли байткоду .pyc .................................................. 66
1.9.2. Оператор from ... import ... ........................................ 67
1.9.3. Ім'я модуля – __name__ ............................................. 67
1.9.4. Створення власних модулів ...................................... 68
1.9.5. Функція dir .................................................................. 69
1.9.6. Пакети ......................................................................... 71
1.10. Типи даних ........................................................................ 72
1.10.1. Цілочислові типи...................................................... 72
1.10.2. Логічні значення....................................................... 75
1.10.3. Тип чисел із плаваючою точкою............................. 75
1.10.4. Рядки.......................................................................... 78
1.10.5. Час і дата ................................................................... 85

363
1.11. Списки, кортежі та словники ............................................... 86
1.11.1. Список ....................................................................... 86
1.11.2. Кортеж....................................................................... 90
1.11.3. Словник ..................................................................... 92
1.11.4. Послідовності ........................................................... 94
1.11.5. Множина ................................................................... 96
1.11.6. Перетворення послідовностей ................................ 97
1.12. Робота з файлами............................................................ 105
1.12.1. Структуровані текстові файли. CSV..................... 106
1.12.2. Модуль pickle.......................................................... 108
1.13. Обробка виняткових ситуацій....................................... 110
1.13.1. Помилки .................................................................. 111
1.13.2. Виняток ................................................................... 111
1.13.3. Обробка винятків ................................................... 111
1.13.4. Виклик винятку ...................................................... 114
1.13.5. Try .. Finally............................................................. 115
1.13.6. Оператор with ......................................................... 116
1.14. Визначення та використання класів ............................. 117
1.14.1. Класи ....................................................................... 118
1.14.2. Методи об'єктів ...................................................... 118
1.14.3. Метод __init__ ........................................................ 119
1.14.4. Змінні класу та об'єкта........................................... 120
1.14.5. Спадкування ........................................................... 123
1.14.6. Метакласи ............................................................... 126
1.15. Проекти ігор.................................................................... 129
1.15.1. Проект № 1. Гра "Шибениця" ............................... 130
1.15.2. Проект № 2. Гра "Пінг-Понг"................................ 161
Розділ 2. Програмування задач мовою Python........................... 183
2.1. Програмування задач на числові послідовності............ 183
2.2. Рекурсія ............................................................................. 221
2.3. Непрямий виклик рекурсії............................................... 242
2.4. Хвостова та вкладена рекурсії ........................................ 243
2.5. Швидке піднесення до степеня
за допомогою рекурсії...................................................... 245
364
2.6. Ханойскі вежі.................................................................... 246
2.7. Нерекурсивний метод ...................................................... 249
2.8. Розширений синтаксис виклику функцій....................... 251
2.9. Структурні типи даних .................................................... 254
2.9.1. Масиви ...................................................................... 255
2.9.2. Використання масивів
як параметрів підпрограм ........................................ 269
2.9.3. Пошук і перестановка елементів масиву ............... 274
2.9.4. Методи сортування елементів масиву.................... 279
2.9.5. Деякі задачі з масивами чисел ................................ 328
2.9.6. Решето Ератосфена .................................................. 335
2.9.7. Схема Горнера для розрахунку
полінома n-го степеня .............................................. 346
2.9.8. Множини................................................................... 352
Література ..................................................................................... 361

365
Навчальне видання

Бичков Олексій Сергійович


Ткаченко Максим Васильович

Практичне
програмування
мовою Python
Підручник

Редактор Н. Земляна

Оригінал-макет виготовлено ВПЦ "Київський університет"

Формат 60х841/16. Ум. друк. арк. 21,4. Наклад 100. Зам. № 219-9485.
Гарнітура Times New Roman. Папір офсетний. Друк офсетний. Вид. № Іт1.
Підписано до друку 20.11.19
Видавець і виготовлювач
ВПЦ "Київський університет"
01601, Київ, б-р Т. Шевченка, 14, кімн. 43
(044) 239 32 22; (044) 239 31 72; тел./факс (044) 239 31 28
e-mail: vpc_div.chief@univ.net.ua; redaktor@univ.net.ua
http: vpc.univ.kiev.ua
Свідоцтво суб'єкта видавничої справи ДК № 1103 від 31.10.02

You might also like