Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

osn_progr_final

.pdf
Скачиваний:
37
Добавлен:
12.02.2016
Размер:
3.27 Mб
Скачать

Іншою локальною подією є кінець послідовності другого масиву, тобто ЕndN (N[j1]= 0). По цій події, скоюємо перехід в початковий стан А і формується код виходу з циклу NonStop=false.

Поки не будуть знайдені співпадаючі елементи – знаходимося в стані С. Тим самим задача розв’язана.

Введемо такі визначення для реалізації СА у вигляді програми. Визначення 5. Говоритимемо, що локальна подія відбулася, як-

що маємо позитивний результат деякого логічного виразу.

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

Пронумеруємо стани СА (за допомогою букв або цифр). Тоді локальні події позначимо з двома індексами, перший з яких позначає поточний стан, а другий – стан, в який повинна перейти програма в наступному циклі при настанні даної локальної події.

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

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

Іншими словами: стан програми, що реалізовує СА – це зациклення на одному і тому ж фрагменті програми до настання локальної події. Позначимо стан як where.

Визначення 7. Переходом назвемо зміну поточного стану в інший стан.

При цьому змінна стану where змінює своє значення.

321

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

Тому, загальний вигляд розв’язання задачі за допомогою СА наступний:

int NonStop = 1; while (NonStop)

{

//тіло автомата;

}

де NonStop ознака продовження циклу, який перед передачею управління оператору while повинен бути встановлений в значення true.

Вся логіка роботи програми полягає в циклічній обробці станів. Вихід з циклу здійснюється по виконанню умови закінчення обробки. У тілі циклу відбувається обробка кожного із станів. Використовуючи позначення з прикладу 1 СА може бути реалізований так:

While (NonStop)

switch(where) // Змінна стану автомата.

{

case А:

//перевірка умов на дугах і петлях і;

//виконання переходів і дій break ;

...

case Z:

//перевірка умов на дугах і петлях і;

//виконання переходів і дій break ;

};

Розглянемо процес програмної реалізації кінцевого автомата детальніше.

Приклад 2. Задача про видалення з тексту коментаря. Нагадаємо, що коментарем називається послідовність символів, що укладена в спеціальні дужки коментаря: /* …*/ [48].

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

322

без збереження. Після досягнення закінчення коментаря знову читаємо і зберігаємо символи. Розберемо цей процес детальніше.

Якщо поточним прочитаним символом є «(«, то далі можливо йтимуть символи становлять коментар, тобто можливий перехід в стан початку коментаря. Це залежить від наступного прочитаного символу. Якщо прочитаний символ «*», то «(« і «*» не зберігаються і процес читання продовжується без збереження поточних символів. Інакше, «(« зберігаємо і читаємо (із збереженням) символи далі. Читання триває до появи символу «*». Це означає, що можливо далі послідує стан закінчення коментаря. Якщо наступний прочитаний символ «)», то коментар закінчений, інакше, продовжуємо читати і не зберігати прочитаний текст.

Таким чином, можна виділити такі стани, в яких знаходиться наша програма:

Поза коментарем. У цьому стані читаємо і записуємо символи тексту.

Стан можливого початку коментаря, тобто прочитаний символ “/”. При читанні наступного символу, можливі такі ситуації.

- поточний прочитаний символ «*». В цьому випадку «(« і «*» не зберігаються, і програма переходить в стан «Усередині коментаря»; - поточний - будь-який інший символ. В цьому випадку, зберігаємо символ «(« і переходимо в стан «Поза коментарем».

Стан усередині коментаря. Якщо прочитаний поточний символ «*», то переходимо в стан «Можливого закінчення коментаря», інакше залишаємося в початковому стані, тобто продовжуємо читати без збереження текст.

Стан можливого закінчення коментаря. Тобто був прочита-

ний символ «*». При читанні наступного символу, можливі такі ситуації. - поточний прочитаний символ «/». В цьому випадку необ-

хідно зберегти попередній символ і

залишитися в стані

«Можливого

закінчення

коментаря»;

- поточним є будь-який інший символ. В цьому випадку здійснюємо перехід в стан «Усередині коментаря».

Складемо діаграму станів.

323

Позначимо через а – читання будь-якого символу окрім «(«; b - читання будь-якого символу окрім «(« і «*»; d – читання будь-якого символу окрім «*» і «)»; А – стан «Поза коментарем»; B – стан «Можливого початку коментаря»; С – стан «Усередині коментаря»; D – стан «Можливого кінця коментаря».

Тоді діаграма станів виглядає таким чином:

a : write(a)

«/«

A

b: write(b)

«/«

«*«

«*« D d

q1

Деталізуємо наш автоматний алгоритм:

B

q1

C

«(«: write(‘(‘)

“*”

a

static char where = ‘A’; int NonStop = 1;

while (NonStop)

{

switch(where)

{

case ‘A’:

// обробка стану А; break;

case ‘B’:

//обробка стану B; break;

case ‘C’:

//обробка стану З; break;

case ‘D’:

//обробка стану D; break;

}

}

324

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

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

Відзначимо, що правильна програма, що реалізовує СА повинна містити дію закінчення циклу, щоб уникнути зациклення в одному стані.

Введемо такі позначення.

Поза коментарем – out;

Можливий початок коментаря – beg_com;

Усередині коментаря – in_com;

Можливий кінець коментаря – end_com.

При читанні першого символу СА знаходиться в початковому стані – where=out. Подальші дії здійснюються залежно від значення where. Наприклад, якщо where=out і current=’/’, то переходимо в стан «початок коментаря» - змінюється стан СА, тобто треба змінній where=beg_com. Якщо читається будь-який інший символ, то необхідно його записати у вихідний файл і СА залишається в колишньому стані.

out: switch(current)

{

case‘/‘: where=beg_com; break; default: putc(current,f_out);

}

Нехай, наприклад СА знаходиться в стані «початок коментаря». Якщо поточний прочитаний символ ‘/’, то записуємо його у вихідний файл і не змінюємо стан СА, якщо поточний символ ‘*’, то переходимо в стан «усередині коментаря» і where=in_com. При читанні будь-якого іншого символу друкуємо його у вихідний файл і переходимо в стан «поза коментарем» where=out.

beg_com: switch(current)

{

case ’/’:putc(current,f_out);break;

325

case ’*’:where=in_com;break; default:

putc(‘/‘,f_out); putc(current,f_out);

where=out;

}

Аналогічно програмуються стани, що залишилися, СА.

Тепер легко можна написати програму, що реалізовує наведений скінченний автомат.

#include<stdio.h>

enum states{out,beg_com, in_com, end_com}; char current;

states where; FILE *f_in,*f_out; main(){ where=out;

f_in=fopen("c:\\text_in.txt","r"); f_out=fopen("c:\\text_out.txt","w"); while (!feof(f_in))

{

current=getc(f_in); switch(where) { case out: switch(current)

{

case'/': where=beg_com; break; default: putc(current,f_out);

}

break;

case beg_com: switch(current)

{

case '/':putc(current,f_out);break; case '*':where=in_com;break; default:

putc('/',f_out); putc(current,f_out); where=out;

}

break;

case in_com: switch(current){ case'*': where=end_com;

}

case end_com: switch(current)

326

{

case '*': ;

case '/': where=out; default: where=in_com;

}

}

}

fclose(f_in); fclose(f_out);

}

Розглянемо ще один приклад.

Приклад 3. З вхідного файлу виділити ідентифікатори, що містять цифри. Припустимо, для простоти, що синтаксично ідентифікатори записані правильно.

Для класифікації послідовностей символів визначимо такі множини:

L – ’A’..’Z’, ’a’..’z’, ’_’;

D – ’0’..’9’;

R – розділювачі ’ ’, ’.’, ’,’, ’:’, ‘;’, ’-’, ’+’, EOL;

Визначимо стани, в яких знаходитиметься СА. I – очікування появи ідентифікатора;

L – обробка ідентифікатора. Очікування появи цифри;

LD – обробка ідентифікатора з цифрою. Очікування кінця ідентифікатора;

Автомат повинен виконувати такі дії: M – пропустити символ;

А – записати символ в буфер;

W – вивести буфер у вихідний файл і очистити; С – очистити буфер.

Складемо діаграму станів .

L: A

R: M

 

L

L: A

I

R: C

 

R: W

327

q1

D: A

D v L: A

Приклад 4. Є послідовність символів x1..xn. Визначити, чи є в ній символи "abcd", що йдуть один за одним. Іншими словами, вимагається з'ясувати, чи є в слові Х підслово "abcd".

Проглядатимемо слово Х зліва направо в очікуванні появи символу 'a'. Як тільки він з'явилася, чекаємо за ним символу 'b', потім 'c', і, нарешті, 'd'.

Таким чином, ми в кожен момент знаходимося в одному з наступних станів: "початкове" (0), "очікування b після а" (A), "очікування с після ab" (B), "очікування d після abc" (C) і "вихід після abcd"

(D).

Складемо діаграму станів.

Not ‘a’

'a’

A

Not ‘a’, ‘b’

Not 'a’, ‘c’

B ‘a’

q1

Not 'a’, ‘d’

 

 

 

'a’

‘b’

 

 

 

 

 

 

'a’

 

 

 

 

 

 

 

 

d: exit

D

 

‘c’

 

 

C

 

 

 

 

 

q1

 

 

 

 

 

 

 

 

 

 

 

Стан Exit означає закінчення роботи.

Напишемо відповідну

програму.

enum states{A,B,C,D}; enum boolean{false,true}; char current;

boolean stop; states where; FILE *f_in;

main(){

where=A;

328

stop=false; f_in=fopen(“c:\\text_in.txt”);

while (!feof(f) || stop)

{

current=getc(f_in); switch (where)

{

case A: switch (current)

{

case‘a‘: where=B;break; default: ;

}

break; case B: switch(current)

{

case ’a’: ;break;

case ’b’: where=C;break; default: where=A;

}

break;

case C: switch(current)

{

case ‘c‘: where=D;break; case ‘a‘: where=B;break; default: where=A;

}

break;

case D: switch(current)

{

case ’d’:

printf(“входить\n”); stop=true;

break;

case ’a’: where=B;break; default: where=A;

}

}

}

if (!stop) printf(“не входить”);

}

ЗАДАЧІ ДЛЯ САМОСТІЙНОЇ РОБОТИ.

1. Побудувати скінченний автомат, що розпізнає мову L, алфавіт А={а,b,c}.

329

а. L тоді і тільки тоді, коли в слові зустрічається не більш три букви а підряд.

би. L тоді і тільки тоді, коли в слові поєднання ab зустрічається не більш двох разів.

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

зустрічається також і буква b.

2.Написати програму для реалізації СА приклада 3.

3.Написати програму, що перевіряє, чи є слово X підсловом слова Y.

4.Написати програму, що перевіряє, чи є у вхідному слові фрагмент вигляду а?b.

5.Написати програму, що перевіряє, чи є у вхідному слові зразок ab*cd . * позначає входження будь-якого слова.

6.Даний список слів X1,...,Xk і слово Y. Визначити, чи входить хоча б одне із слів Xi в слово Y, як підслово. Кількість дій не повинна перевершувати константи, помноженої на сумарну довжину всіх слів (із списку і того, в якому відбувається пошук).

6.Скласти діаграму станів та написати програму для реализації СА, що розпізнає числа з плаваючою точкою.

330

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]