
Зубенко, Омельчук - Програмування. Поглиблений курс
.pdf
|
|
Розділ ІІ. ЕЛЕМЕНТИ ІНФОРМАТИКИ |
||
биттів числа n із доданками, |
що не перевищують m . Нескладно пе- |
|||
ревірити, що наступне ІОФС задає пару функцій [f ,q]: |
||||
(БФС18 ) q (1,m) = q (n,1) =1 , |
f (1) =1. |
|
|
|
(ІФС18 ) q (n,m) |
(m ≥ n →1 |
+ q (n,n −1)| |
= |
q (n,n )■ |
= |
|
|||
q (n −m,m)+ q (n,m −1)), f (n ) |
|
|||
|
|
|
|
|
Усі наведені ІОФ мають спільну рису – значення індуктивної функ- ції на своїх аргументах при індуктивному переході визначаються за допомогою її ж значень на простіших аргументах. Таку індукцію бу- демо називати індукцією типу згортки. Іншим, дуальним, варіантом індуктивних означень є означення функцій типу розгортки, коли значення функції на аргументах при індуктивному переході визна- чаються за допомогою її ж значень на заданих або нових аргументах, отриманих за допомогою конструкторів області визначення функції. У цьому випадку база індукції задає умову завершення індуктивних переходів по зовнішньому фронту області визначення функції.
Проілюструємо цей тип індукції на прикладі 91-шої функції Мак- Картні, яка задається таким ІОФ (див. підрозд. 1.3.3):
(БФ) f91 (x ) = x −10, для x >100 ;
(ІФ) f91 (x ) = f91(f91(x +11)) для x ≤100 .
Тут конструктор h збігається з функцією f91 і застосовується до
значення означуваної функції на новому аргументі x +11. Як і у ви- падку індукції типу згортки, для обчислення значення такої функції f91 можна застосувати правило повної підстановки. Обчислимо, на-
приклад, значення f91(99): t0 = f91(99), t1 = f91(f91(110)) ,
t2 = f91(f91(110)) = f91(100) = f91(f91(111)), t3 = f91(101) = 91.
Загальне індуктивне означення функцій типу розгортки (ІОФР) роз- глянемо на прикладі бінарної функції f : M ×M → A . Воно має вигляд:
(БФР) Для x,y D0 : f (x,y) = g (x,y), де g (x,y) – відома функція, задана на деякій підмножині пар D0 M ×M .
(ІФР) Є один або кілька конструкторів значень функції вигляду
h : M n × An×m → A |
таких, |
що |
|
для |
будь-яких |
|
|
|
n,m |
, де |
z1,...,zk або збі- |
(x,y) M \ D0 : f (x,y)= h z1,...,zn , f (zi ,z j ) |
|
||||
|
|
|
i =1, j =1 |
|
|
231

ПРОГРАМУВАННЯ
гаються з одним із x,y , або є теоремами, виведеними в M із базових елементів і гіпотез x,y .
(ПФР) Вибирається певний фільтр P (x,y,z ), визначений на універ- сумі M ×M × A , що здійснює відбір результатів означуваної функції.
Як ми вже бачили, конструктор h може збігатися з функцією f , а бі- льшість його параметрів на практиці є фіктивними. Як і раніше, функція f може мати інші аргументи, за якими не здійснюється індукція.
Аналогічний вигляд мають індуктивні означення систем функцій типу розгортки (ІОФСР). Розглянемо два приклади побудови таких індуктивних означень.
Приклад 2.43. Розкладання числа на прості множники.
|
Знайдемо систему функцій [ f ,g] , f : N → N *,g : N ×N → N * , в якій |
|
f |
продукує розкладання натурального числа на прості множники, а |
|
g |
є допоміжною функцією, що знаходить усі дільники даного числа, |
|
які не менші ніж другий аргумент. Наприклад, |
f (24) = [2,2,2,3]. |
|
|
(БФСР16 ) f (1) = g (1,x ) = ε для всіх x N , g (x,y) = pre(x,ε) для всіх |
|
1 < x < y2 і в x немає дільників менших ніж y . |
|
|
|
(ІФСР16 ) Для будь-яких x ,y >1: |
|
|
f (x ) = ((x%2 = 0 → pre(2, f (x /2)))|g (x,3)), |
|
|
(x =1 → ε| |
|
|
g (x,y) = (x < y2 → [x]| |
. |
|
|
|
|
(x%y = 0 → pre(y,g (x /y,y ))|g (x,y + 2)))) |
|
|
|
|
Означення ІОФСР16 є дійсно індуктивним типу розгортки, оскільки праворуч є виклики g , в яких x не змінюється, а y збільшується на 2 ■
Приклад 2.44. Задача про n ферзів (правила гри в шахи див. у
підрозд. 2.4.4).
Необхідно знайти всі позиції n ферзів на шаховій дошці розміром n ×n , в яких вони не загрожують один одному Нехай
Z = {0,1,...,n},W = {0,1,...,n −1}, Bool = {0,1}. Функція Queen : N → (W * )*
генерує всі розв'язки задачі. Інші функції в системі:
232

Розділ ІІ. ЕЛЕМЕНТИ ІНФОРМАТИКИ
BackTrack :W * × Z ×(W * )* , Q : Z ×W * ×W → Bool , P :W * ×W → Bool ,u(p u):W * ×W → Bool , Ju(p u):W * ×W →W , h :W * →W * , L :W * → Z ,
[ ]:W * ×W → Z є допоміжними. Конфігурації ферзів подаються сло- вами a1a2...ar в алфавіті W . Слово 530 подає конфігурацію трьох
ферзів. Вони розташовані в перших трьох вертикалях дошки: на пе- ршій вертикалі ферзь стоїть на шостій клітині (нумерація клітин ве- ртикалі починається з 0), на другій – на четвертій позиції, на третій вертикалі – на першій позиції. Функція BackTrack реалізує бектре- кінг і генерує найближчу (у лексикографічному порядку) до x вірну конфігурацію ферзів, розташованих у перших кількох поспіль вер- тикалях. Якщо довжина чергової вірної конфігурації n, то вона до- дається до списку. Відповідне ІОФС має вигляд:
(БФС17 ) BackTrack (ε,k,y)= y , h(ε)= ε , x p u = 0 для k = n ,
k
|
( |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[ ] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||
Q |
|
j,x,k |
|
= |
1 |
|
|
для j > |
x |
, ax 1 |
|
= a |
для довільного x , |
|
ε |
= 0 . |
|
|
|
|
||||||||||||||||||||||||||||||||||||||
|
(ІФС17 ) Для будь-яких n > 3 , |
x W * , y (W * )* , |
0 ≤k ≤ n −1 : |
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Queen(n )= BackTrack ( |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||||||||||||||||||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1,3,ε), |
|
|
|
|
|
|
|
|
|
|
|
|
||||||||||||||||||||||||||
|
BackTrack (x,k,y)= |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|||||||||||||||||||
|
|
|
x |
|
=n →BackTrack(h(x),L(x) +1,pre(x,y))| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||||||||||||||||||||||||||||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||||||||||||||||||||||||||||||||||||
= |
|
|
x |
|
<n |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|||||||||
|
|
|
|
→( u(x pu) →BackTrack(Ju(x p u),0,y)|BackTrack(h(x),L(x) +1,y)) |
||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
k |
( |
|
|
|
|
( |
|
|
|
)) |
|
|
|
|
( |
k |
( |
|
|
|
)) |
|
|
|
|
|
|
( |
|
|
|
|
|
) |
|
( |
|
) |
|
||||
|
|
|
ax |
|
= |
1+ |
x |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||||||||||||||||||||||||
|
|
|
|
|
|
, h |
|
app |
|
|
z,a |
|
= z , |
L |
|
|
app |
|
z,a |
|
|
|
= a , P |
|
x,k |
|
= Q 1,x,k |
|
, |
|||||||||||||||||||||||||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
( |
k |
|
) |
|
|
(x,k |
)→ |
|
|
|
|
( |
|
|
|
k +1 ) |
|
|
||||||||||||
|
|
|
|
|
|
|
|
|
|
ax [k]= x [k −1], u |
|
x pu |
|
|
= P |
|
|
|
x p u |
, |
|
|
||||||||||||||||||||||||||||||||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
1| u |
|
|
|
||||||||||||||||||||||||||||||||||||||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
( |
|
|
k |
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
( |
|
|
k +1 ) |
|
|
|
|
|
|
|||||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Ju |
|
x pu |
|
|
= |
P (x,k |
)→ x ok |
|Ju |
|
x p u |
|
, |
|
|
|
|
|
|
Q(j,x,k )= ((x [j ]= k (x [j ]−k = x +1− j ))→ 0|Q (j +1,x,k )).
Щодо коректності означень ІОФСР16 та ІОФСР17 див. вправу 15 ■
Увага! Індуктивно означені функції як відображення мають також денотаційну семантику, основану на теоремі про нерухому точку не- перервних функцій (див. підрозд. 1.2.5). Уперше звернули на це увагу Д. Скот і К. Стречі (див. літературу для СР) ►
233

ПРОГРАМУВАННЯ
2.6.3. РЕКУРСИВНІ СПЕЦИФІКАЦІЇ
Сучасні мови високого рівня й мови специфікацій або прямо під- тримують індуктивні означення таких важливих структур даних, як натуральні числа, послідовності однотипних елементів (числові, сим- вольні), Б-дерева, списки (у тому числі й списки списків) тощо (мови
Lisp, Меta IV, Z, RSL, HOPE, МL, Python та ін.), або дозволяють ефек-
тивно їх моделювати за допомогою покажчиків. Специфікація функ- ції називається рекурсивною, якщо її тіло містить власні виклики. На- явність у мові програмування рекурсивних функцій забезпечує мож- ливість реалізації індуктивно означених функцій та їхніх систем. При цьому індуктивні означення або просто кодуються відповідними мов- ними засобами, або спочатку програмується відповідна алгебра (мо- делюються об'єкти й конструктори), а вже потім на цій основі здійс- нюється трансформація індуктивних означень функцій у програми.
Важливим питанням рекурсивних специфікацій функцій є склад- ність алгоритмів їхнього обчислення (див. підрозд. 3.6.3). Це стосуєть-
ся, насамперед, просторової складності PA (n ). Справа в тім, що при
черговому рекурсивному виклику функції відбувається переривання й консервація в системному стеку поточного виклику. Максимальна кількість таких перерваних і незавершених викликів називається глибиною рекурсії для даного початкового виклику. Зазвичай ця кіль- кість прямо залежить від вхідних параметрів виклику. Наприклад, для функцій, що обчислюють довжину слова (див. прикл. 2.38) і вагу Б-дерева (прикл. 2.39), глибина рекурсії збігається відповідно з дов- жиною вхідного слова й висотою дерева.
Увага! Ураховуючи, що розмір системного стеку стандартний (у ба- гатьох ОС він не перевищує одного сегмента), глибина рекурсії може виявитися занадто великою для даного операційного середовища. Як наслідок, виконання програми може перериватися з повідомленням про нестачу місця на системному стеку ►
У деяких складних випадках на завершальних етапах розробки ре- курсивні програми замінюють їхніми більш надійними ітеративними варіантами. Цей процес називається елімінацією рекурсії. При цьому застосовують як загальні, так і спеціальні прийоми елімінації. Загаль- ні теж використовують стек, але не системний, а програмний. Спеці- альні враховують специфіку конкретних схем рекурсії.
234

Розділ ІІ. ЕЛЕМЕНТИ ІНФОРМАТИКИ
*Література для CР: Індуктивні означення множин і функцій – [121, 140, 146]; теорія нерухомої точки для рекурсивних функцій – [82, 118, 119, 148]; рекурсивні специфікації – [2, 139]; елімінація рекурсії – [1, 125, 139].
Контрольні запитання та вправи
1.Дати індуктивне означення: а) множини; б) систем множин. Навести приклади.
2.Яка роль конструкторів і фільтра в ІО?
3.Що таке повнота й повнота слабка в ІО?
4.Що таке виведення елемента за ІО?
5.Що таке гіпотеза й висновок в ІО?
6.Що таке аксіома й теорема в ІО?
7.Дати індуктивне означення функції й систем функцій.
8.Що таке індуктивна функція?
9.Сформулювати правило повної підстановки для обчислення значень індуктивних функцій.
10.Навести приклади ІОФ, для яких правила повної підстанов- ки й виклик за значенням приводять до різних значень [82].
11.У чому полягає різниця між ІОФ типу згортки й розгортки?
12.Що таке рекурсивна специфікація функцій?
13.Побудувати ІО для мов: 1) L2 із прикл. 1.24; 2) Діка D2n .
14.Показати, що строгість індуктивного означення області ви- значення індуктивної функції не є необхідною умовою її де- термінованості. Навести відповідні приклади.
15 * . Обґрунтувати за допомогою структурної індукції корект- ність індуктивних означень функцій із прикл. 2.37–2.44.
16.Нехай u та v – довільні слова в алфавiті Σ. Дати індуктивні означення для наведених словарних функцій. Припустими- ми операціями в конструкторах є стандартні арифметичні операції й операції на словах – взяття голови та хвоста сло-
ва, конкатенація слів і порівняння символів з Σ: а) ne(u,v ) перевіряє нерівність u ≠ v ;
б) eq (u,v ) перевіряє рівність u = v ; в) gt (u,v ) перевіряє нерівність u > v ;
г) addsym(u,c,k ) вставляє на k -ту позицію в u символ c ; д) delsym(u,k ) усуває в u k -й символ.
235

ПРОГРАМУВАННЯ
17. Дати індуктивні означення арифметичних функцій, n > 0 : а) count (n ), що обчислює кількість цифр у числі n ;
б) f (n ), що міняє старшу й молодшу цифри в числі n ;
в) * |
prime(n ), що перевіряє число n на простоту; |
|
г) * |
perfect (n ), що перевіряє число n на досконалість27. |
|
18. Індуктивне означення натуральної функції f (x1,...xn ) |
на- |
|
зивається примітивно-рекурсивним, якщо воно має вигляд: |
||
(БФ) f (x1,...xn −1,0) = g (x1,...xn −1 ) для деякої функції g ; |
||
(ІФ) |
f (x1,...xn −1,s (y))= h (x1,...xn −1,y, f (x1,...xn −1,y)) |
для |
деякого конструктора h .
Базовими назвемо функції s (x ),O (x ) = 0 і функції проек-
ції Imn (x1,...xn ) = xm ,m =1,n . Функція називається примі-
тивно-рекурсивною, якщо вона отримана з базових скін- ченною кількістю застосувань операцій суперпозиції та примітивно-рекурсивних означень. Показати, що функції примітивно-рекурсивні:
а) x + y ; б) x ×y ; в) x /y ; г) x%y ; д) xy ; е) sg (x )= (x = 0 → 0|1);
є) x −& y = (x ≥ y → x − y|0); ж) prime(n ) із вправи 17; з) perfect (n ) із вправи 17; и) π(n ) – кількість простих чисел, не більших ніж n .
19.Побудувати індуктивне означення функції, що обраховує кількість листків бінарного дерева.
20.Побудувати індуктивне означення функції, що знаходить висоту бінарного дерева.
21.Побудувати індуктивне означення функції, що усуває у зваженому дереві всі вузли із заданою вагою.
27 Натуральне число називається досконалим, якщо воно збігається із сумою всіх сво- їх власних дільників.
236

Розділ ІІ. ЕЛЕМЕНТИ ІНФОРМАТИКИ
стиль програмування, мова специфікацій, мова UML, сутності й відношення UML, діаграми використання (прецедентів), класів, поведінки, взаємодії й реалізації, прикордонні й керівні класи, класи-сутності, абстрактні й конкретні класи, відношення між класами: узагальнення, залежності, асоціації, агрегації, композиції, діаграми станів і діяльності, діаграми компонентів і розгортання, рефакторинг, налагоджувальні оператори, твердження, стопори помилок, тестування граничних умов, тестування білої й чорної скриньок, модульне та інтеграційне тестування.
У підрозд. 2.2 були розглянуті поняття життєвого циклу інформа- ційних систем і його реалізації. Продовжимо розгляд цих та інших питань, але з практичнішого погляду. Матеріал не орієнтований на вивчення конкретних середовищ розробки систем, тому ми наводимо лише огляд деяких важливих можливостей сучасних ТхП.
Увага! Для ілюстрації елементів ТхП використовується мова C/C++ (див. розд. 3) ►
2.7.1. СТИЛЬ
Написати програму – це більше, ніж правильно її оформити та змусити коректно виконуватись. Програми згодом неминуче модифікуються, тому велике значення має якість їхньої структури, що безпосередньо залежить від простоти сприйняття та зрозумілості. Інколи добре написану чужу програму набагато простіше зрозуміти, ніж власну, але написану погано. Культура написання коду сприяє зменшенню помилок у програмах і полегшує їхню модифікацію.
Під стилем програмування зазвичай розуміють набір прийомів і методів, що застосовуються з метою одержання правильних і ефективних програм, зручних для сприйняття, застосування й модифікації.
237

ПРОГРАМУВАННЯ
Вибір імені. Розпочнемо з простого на перший погляд питання вибору імені. Імена мають самі програми, дані, типи, класи, функції, методи. Коли їх у програмі з десяток чи два, то вибір не є дуже принциповим питанням. Однак коли їхня кількість у системі сягає десятків, сотень, а то й тисяч, то, щоб не втратити контроль над системою, при виборі імен необхідно дотримуватись певної строгої дисципліни й порядку. Дамо кілька загальних порад.
Доцільно використовувати осмислені імена для глобальних змінних і, за можливості, короткі – для локальних. Глобальні змінні, функції, класи та структури за означенням можуть з'являтись у довільному місці програми, тому найважливішою для них є інформативність, а не довжина. Краще використовувати глобальні імена так, щоб вони явно вказували на смисл понять, що ними подаються, тобто мали відповідну мнемоніку. Корисно опис кожної глобальної змінної, функції, типу даних, константи супроводжувати коротким коментарем.
Приклад 2.45. Стиль найменувань.
/*структура, що задає дату*/ struct date {
int day; /*день*/ int month; /*місяць*/ unsigned year; /*рік*/ };
/*Друкування дати*/
void printDate(struct date *pd);
іnt iCountElements=0; /*поточна кількість елементів*/
■
Однак коментарі не мають містити очевидної інформації на зразок того, що оператор i++ збільшує змінну i тощо.
Для локальних змінних, навпаки, краще підходять короткі імена. Для використання всередині функції цілком підійдуть імена i, j, n, npoіnts тощо. При цьому для лічильників циклу зазвичай використовуються імена i, j, для покажчиків – p, q, для рядків – s, t. Якщо це можливо, використовують англомовні назви змінних, типів, класів, функцій, констант, які відповідають їхній ролі в програмі.
Існує багато корпоративних домовленостей і традицій іменування. Традиційно для функцій використовуються імена, побудовані з діє- слів та іменників, причому перші літери слів великі (LoadData()), а для змінних – іменники.
238

Розділ ІІ. ЕЛЕМЕНТИ ІНФОРМАТИКИ
Оператори й вирази. Ці основні конструкції програм слід писати так, щоб їхній зміст був максимально зрозумілим. Найпростіший спо- сіб досягти цього – форматування коду за допомогою відступів, табу- ляцій, порожніх рядків. Розглянемо два тексти однієї й тієї самої про- грами. Очевидно, що другий текст більш читабельний саме завдяки використанню його якісного форматування.
Приклад 2.46. Форматування коду. Лістинг 1.
/*бінарний пошук без форматування тексту*/
int BinarySearch (char *items, int count, char key) {int low,high,mid;
low=0; high=count-1; while(low<=high){ mid=(low+high)/2; if(key<items[mid])high=mid-1;
else if(key>items[mid]) low=mid+1; else return mid;}
return -1;}
Лістинг 2.
/*бінарний пошук із форматуванням тексту*/
int BinarySearch (char *items, int count, char key)
{
int low=0, high=count-1, mid; while(low<=high) {
mid=(low+high)/2;
if(key<items[mid]) high=mid-1;
else if(key>items[mid])
low=mid+1; else
return mid; /*ключ знайдено*/
}
return -1; }■
Іноді можуть поліпшити сприйняття виразу додаткові дужки, що підкреслюють структуру коду. Їх застосовують, навіть якщо синтаксично в цьому немає потреби. Зрозумілість і стислість коду – не одне й те саме. Головним критерієм вибору синтаксису для коду має бути простота його сприйняття.
239

ПРОГРАМУВАННЯ
Відступи, табуляції, переходи на новий рядок допомагають краще структурувати код. Однак якому стилю тут віддати перевагу? Наприклад, чи варто розташовувати відкриваючу фігурну дужку в тому самому рядку, що й іf або while, чи в наступному? Єдиного рецепта немає. Важлива не стільки конкретика вибраного стилю, скільки логічність і послідовність його застосування.
Вважається, що одна функція (метод) не має займати більш ніж один екран консолі. Якщо це не так, то їх краще розбити на дрібніші. Якщо певний фрагмент коду вимагає коментування, то його теж краще виділити в окрему функцію (метод).
2.7.2. СПЕЦИФІКАЦІЇ СИСТЕМИ
Сучасні програмні проекти реалізуються колективами різних фахів- ців – від аналітиків, проектувальників, кодувальників, тестувальників до менеджерів проектів, кожний з яких виконує свою роботу на різних етапах ЖЦ. Для фіксації й обміну результатами роботи використовують спеціальні ДС, що отримали назву мов специфікації. Наприклад, як ми вже знаємо з підрозд. 2.2.1, результатом роботи аналітиків є зовнішня специфікація системи, а кодувальників – програмний код.
Специфікації описують ту чи іншу модель вхідної системи або її частини, тобто моделюють систему й можуть бути використані за- мість неї в деяких контекстах. За властивостями специфікацій сис- теми можна робити висновки щодо властивостей самої системи.
При побудові специфікації має бути чітко поставлена мета: для чо- го дана модель створюється, які завдання вона повинна розв'язати тощо. Зазвичай мета задає набір властивостей, що мають бути відо- бражені в моделі, і контекст, в якому систему можна замінити модел- лю. Наприклад, кінцевою метою етапу проектування є отримання специфікації системи у вигляді, за яким легко писати код програми.
Основними вимогами до специфікацій є їхня повнота, точність і зрозумілість.
Специфікації можуть використовуватися:
•для уточнення вимог, узгоджень їх із замовником і побудови про- тотипів;
•при проектуванні – для контролю за правильністю проекту;
•при реалізації – для формулювання завдань розробникам і ство- рення документації;
•при тестуванні – для перевірки виконання вимог;
•при супроводженні – для уточнення змін, підтримки узгодженості документації із системою тощо.
240