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

Программирование Cи / Богатырев_Язык Си в системе Unix

.pdf
Скачиваний:
80
Добавлен:
25.04.2015
Размер:
1.19 Mб
Скачать

А. Богатырёв, 1992-96

- 271 -

Си в UNIX™

void main(int ac, char *av[]){

register char *s, *p; register n; extern int nbra; extern char *braslist[], *braelist[], *loc1, *loc2;

if( ac > 1 && !strcmp(av[1], "-a")){ ac--; av++; all++; } if(ac != 3){

fprintf(stderr, "Usage: %s [-a] pattern subst\n", av[0]); exit(1);

}

compile(av[1], compiled, compiled + sizeof compiled, EOL);

while( gets(line) != NULL ){

if( !step(s = line, compiled)){ printf("%s\n", line); continue;

}

do{

/* Печатаем начало строки */

for( ; s != loc1; s++) putchar(*s);

/* Делаем замену */ for(s=av[2]; *s; s++) if(*s == '\\'){

if(isdigit(s[1])){ /* сегмент */ int num = *++s - '1';

if(num < 0 || num >= nbra){

fprintf(stderr, "Bad block number %d\n", num+1); exit(2);

}

for(p=braslist[num]; p != braelist[num]; ++p) putchar(*p);

} else if(s[1] == '&'){

++s; /* вся сопоставленная строка */ for(p=loc1; p != loc2; ++p)

putchar(*p);

} else putchar(*++s);

}else putchar(*s);

}while(all && step(s = loc2, compiled));

/* Остаток

строки */

 

for(s=loc2; *s; s++) putchar(*s);

 

putchar('\n');

 

} /* endwhile */

 

}

 

 

void regerr(int code){ char *msg;

 

switch(code){

 

 

case 11: msg = "Range endpoint too large.";

break;

case 16: msg = "Bad number.";

break;

case 25: msg = "\\digit out of range.";

break;

case 36: msg = "Illegal or missing delimiter."; break;

case 41: msg = "No remembered search string.";

break;

case 42: msg = "\\(~\\) imbalance.";

break;

case 43: msg = "Too many \\(.";

break;

case 44: msg = "More than 2 numbers given in \\{~\\\"}."; break; case 45: msg = "} expected after \\."; break;

case 46: msg = "First number exceeds second in \\{~\\}."; break;

case 49: msg = "[ ] imbalance.";

break;

case 50: msg = "Regular expression overflow.";

break;

default: msg = "Unknown error";

break;

} fputs(msg, stderr); fputc('\n', stderr); exit(code);

}

А. Богатырёв, 1992-96

- 272 -

Си в UNIX™

void prfields(){

 

 

int i;

 

 

for(i=0; i < nbra; i++)

 

prfield(i);

 

}

 

 

void prfield(int n){

 

 

char *fbeg = braslist[n], *fend = braelist[n];

 

printf("\\%d='",

n+1);

 

for(; fbeg != fend; fbeg++) putchar(*fbeg);

printf("'\n");

}

7.44. Составьте функцию поиска подстроки в строке. Используя ее, напишите программу поиска подстроки в текстовом файле. Программа должна выводить строки (либо номера строк) файла, в которых встретилась данная подстрока. Подстрока задается в качестве аргумента функции main().

/* Алгоритм быстрого поиска подстроки.

*Дж. Мур, Р. Бойер, 1976 Texas

*Смотри: Communications of the ACM 20, 10 (Oct., 1977), 762-772

*Этот алгоритм выгоден при многократном поиске образца в

*большом количестве строк, причем если они равной длины -

*можно сэкономить еще и на операции strlen(str).

*Алгоритм характерен тем, что при неудаче производит сдвиг не на

*один, а сразу на несколько символов вправо.

*В лучшем случае алгоритм делает slen/plen сравнений.

*/

 

char *pattern;

/* образец (что искать) */

static int plen;

/* длина образца */

static int d[256];

/* таблица сдвигов; в алфавите ASCII -

 

* 256 букв. */

/* расстояние от конца образца до позиции i в нем */

#define DISTANCE(i)

((plen-1) - (i))

А. Богатырёв, 1992-96

- 273 -

Си в UNIX™

/* Поиск:

*выдать индекс вхождения pattern в str,

*либо -1, если не входит

*/

int indexBM( str ) char *str; /* в чем искать */

{

int slen = strlen(str); /* длина строки */

register int pindx; /* индекс сравниваемой буквы в образце */ register int cmppos; /* индекс сравниваемой буквы в строке */ register int endpos; /* позиция в строке, к которой "приставляется"

* последняя буква образца */

/* пока образец помещается в остаток строки */ for( endpos = plen-1; endpos < slen ; ){

/* Для отладки: pr(str, pattern, endpos - (plen-1), 0); /**/

/* просмотр образца от конца к началу */ for( cmppos = endpos, pindx = (plen - 1);

pindx >= 0 ; cmppos--, pindx-- )

if( str[cmppos] != pattern[pindx] ){

/* Сдвиг, который ставит самый правый в образце

*символ str[endpos] как раз под endpos-тую

*позицию строки. Если же такой символ в образце не

*содержится (или содержится только на конце),

*то начало образца устанавливается в endpos+1 ую

*позицию

*/

endpos

+= d[ str[endpos] & 0377 ];

break;

/* & 0377 подавляет расширение знака. Еще */

}/* можно сделать все char -> unsigned char */

if( pindx < 0 ) return ( endpos - (plen-1)); /* Нашел: весь образец вложился */

}

 

return( -1 );

/* Не найдено */

}

 

/* Разметка таблицы сдвигов */

void compilePatternBM( ptrn ) char *ptrn; { register int c;

pattern = ptrn; plen = strlen(ptrn);

/* c - номер буквы алфавита */ for(c = 0; c < 256; c++)

d[c] = plen;

/* сдвиг на длину всего образца */

/* c - позиция в образце */ for(c = 0; c < plen - 1; c++)

d[ pattern[c] & 0377 ] = DISTANCE(c); /* Сдвиг равен расстоянию от самого правого

*(кроме последней буквы образца)

*вхождения буквы в образец до конца образца.

*Заметим, что если буква входит в образец несколько раз,

*то цикл учитывает последнее (самое правое) вхождение.

*/

}

А. Богатырёв, 1992-96

- 274 -

Си в UNIX™

/* Печать найденных строк */

 

void pr(s, p, n, nl) char *s, *p;

 

{

 

 

register i;

 

 

printf("%4d\t%s\n", nl, s );

 

printf("

\t");

 

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

 

putchar( s[i] == '\t' ? '\t' : ' ' );

 

printf( "%s\n", p );

 

}

 

 

/* Аналог программы fgrep */

 

#include <stdio.h>

 

 

char str[ 1024 ];

/* буфер для прочитанной строки */

 

void main(ac, av) char **av;

{

int nline = 0; /* номер строки файла */ int ind;

int retcode = 1;

if(ac != 2){

fprintf(stderr, "Usage: %s 'pattern'\n", av[0] ); exit(33);

}

compilePatternBM( av[1] ); while( gets(str) != NULL ){

nline++;

if((ind = indexBM(str)) >= 0 ){ retcode = 0; /* O'KAY */ pr(str, pattern, ind, nline);

}

}

exit(retcode);

}

/* Пример работы алгоритма:

peter piper picked a peck of pickled peppers. peck

peter piper picked a peck of pickled peppers. peck

peter piper picked a peck of pickled peppers. peck

peter piper picked a peck of pickled peppers. peck

peter piper picked a peck of pickled peppers. peck

peter piper picked a peck of pickled peppers. peck

peter piper picked a peck of pickled peppers. peck

peter piper picked a peck of pickled peppers. peck

*/

7.45.Напишите аналогичную программу, выдающую все строки, удовлетворяющие упрощенному регулярному выражению, задаваемому как аргумент для main(). Используйте функцию match, написанную нами ранее. Вы написали аналог программы grep из UNIX (но с другим типом регулярного выражения, нежели в оригинале).

7.46.Составьте функцию expand(s1, s2), которая расширяет сокращенные обозначения вида a-z строки s1

в эквивалентный полный список abcd...xyz в строке s2. Допускаются сокращения для строчных и прописных букв и цифр. Учтите случаи типа abc, az09 и −ag (соглашение состоит в том, что символ "−", стоящий в начале или в конце, воспринимается буквально).

А. Богатырёв, 1992-96

- 275 -

Си в UNIX™

7.47. Напишите программу, читающую файл и заменяющую строки вида

|<1 и более пробелов и табуляций><текст>

на пары строк

|.pp

|<текст>

(здесь | обозначает левый край файла, a <> - метасимволы). Это - простейший препроцессор, готовящий текст в формате nroff (это форматтер текстов в UNIX). Усложнения:

• строки, начинающиеся с точки или с апострофа, заменять на

\&<текст, начинающийся с точки или '>

• строки, начинающиеся с цифры, заменять на

.ip <число> <текст>

символ \ заменять на последовательность \e.

удалять пробелы перед символами .,;:!?) и вставлять после них пробел (знак препинания должен быть приклеен к концу слова, иначе он может быть перенесен на следующую строку. Вы когда-нибудь видели строку, начинающуюся с запятой?).

склеивать перенесенные слова, поскольку nroff делает переносы сам:

....xxxx начало-

=> ....xxxx началоконец

конец yyyy......

yyyy................

Вызывайте этот препроцессор разметки текста так:

$prep файлы... | nroff -me > text.lp

7.48.Составьте программу преобразования прописных букв из файла ввода в строчные, используя при этом функцию, в которой необходимо организовать анализ символа (действительно ли это буква). Строчные буквы выдавать без изменения. Указание: используйте макросы из <ctype.h>.

Ответ:

#include <ctype.h> #include <stdio.h> main(){

int c;

while( (c = getchar()) != EOF ) putchar( isalpha( c ) ?

(isupper( c ) ? tolower( c ) : c) : c);

}

либо ...

putchar( isalpha(c) && isupper(c) ? tolower(c) : c ); либо даже

putchar( isupper(c) ? tolower(c) : c );

В последнем случае под isupper и islower должны пониматься только буквы (увы, не во всех реализациях это так!).

7.49. Обратите внимание, что если мы выделяем класс символов при помощи сравнения, например:

char ch;

if( 0300 <= ch && ch < 0340 ) ...;

(в кодировке КОИ-8 это маленькие русские буквы), то мы можем натолкнуться на следующий сюрприз: перед сравнением с целым значение ch приводится к типу int (приведение также делается при использовании char в качестве аргумента функции). При этом, если у ch был установлен старший бит (0200), произойдет расширение его во весь старший байт (расширение знакового бита). Результатом будет отрицательное целое число! Опыт:

char c = '\201';

/* = 129 */

printf( "%d\n", c );

 

печатается -127. Таким образом, наше сравнение не сработает, т.к. оказывается что ch < 0. Следует подавлять расширение знака:

if( 0300 <= (ch & 0377) && (ch & 0377) < 0340) ...;

(0377 - маска из 8 бит, она же 0xFF, весь байт), либо объявить

А. Богатырёв, 1992-96

- 276 -

Си в UNIX™

unsigned char ch;

что означает, что при приведении к int знаковый бит не расширяется.

7.50. Рассмотрим еще один пример:

main(){ char ch;

/* 0377 - код последнего символа алфавита ASCII */ for (ch = 0100; ch <= 0377; ch++ )

printf( "%03o %s\n", ch & 0377,

ch >= 0300 && ch < 0340 ? "yes" : "no" );

}

Какие неприятности ждут нас здесь?

во-первых, когда бит 0200 у ch установлен, в сравнении ch выступает как отрицательное целое число (т.к. приведение к int делается расширением знакового бита), то есть у нас всегда печатается "no". Это мы можем исправить, написав unsigned char ch, либо используя ch в виде

(ch & 0377)

или

((unsigned) ch)

во-вторых, рассмотрим сам цикл. Пусть сейчас ch =='\377'. Условие ch <= 0377 истинно. Выполняется оператор ch++. Но ch - это байт, поэтому операции над ним производятся по модулю 0400 (0377 - это максимальное значение, которое можно хранить в байте - все биты единицы). То есть теперь значе-

нием ch станет 0. Но 0 < 0377 и условие цикла верно! Цикл продолжается; т.е. происходит зацикливание. Избежать этого можно только описав int ch; чтобы 0377+1 было равно 0400, а не 0 (или unsigned int, лишь бы длины переменной хватало, чтобы вместить число больше 0377).

7.51. Составьте программу, преобразующую текст, состоящий только из строчных букв в текст, состоящий из прописных и строчных букв. Первая буква и буква после каждой точки - прописные, остальные - строчные.

слово один. слово два. --> Слово один. Слово два.

Эта программа может оказаться полезной для преобразования текста, набранного в одном регистре, в текст, содержащий буквы обоих регистров.

7.52. Напишите программу, исправляющую опечатки в словах (spell check): программе задан список слов; она проверяет - является ли введенное вами слово словом из списка. Если нет - пытается найти наиболее похожее слово из списка, причем если есть несколько похожих - выдает все варианты. Отлавливайте случаи:

две соседние буквы переставлены местами: ножинцы=>ножницы;

удвоенная буква (буквы): ккаррандаш=>карандаш;

потеряна буква: бот=>болт;

измененная буква: бинт=>бант;

лишняя буква: морда=>мода;

буквы не в том регистре - сравните с каждым словом из списка, приводя все буквы к маленьким:

сОВОк=>совок;

Надо проверять каждую букву слова. Возможно вам будет удобно использовать рекурсию. Подсказка: для некоторых проверок вам может помочь функция match:

слово_таблицы = "дом";

if(strlen(входное_слово) <= strlen(слово_таблицы)+1 && match(входное_слово, "*д*о*м*") ... /* похоже */

*о*м*

?дом

дом?

*д*м*

д?ом

 

*д*о*

до?м

 

Приведем вариант решения этой задачи:

А. Богатырёв, 1992-96

- 277 -

Си в UNIX™

#include <stdio.h> #include <ctype.h> #include <locale.h>

typedef unsigned char uchar; #define ANYCHAR '*'

/* символ, сопоставляющийся с одной любой буквой */

static

uchar

version[120];

/* буфер для генерации вариантов */

static

uchar

vv;

/* буква, сопоставившаяся с ANYCHAR */

/* привести все буквы к одному регистру */ static uchar icase(uchar c){

return isupper(c) ? tolower(c) : c;

}

/* сравнение строк с игнорированием регистра */ static int eqi(uchar *s1, uchar *s2 )

{

while( *s1 && *s2 ){

if( icase( *s1 ) != icase( *s2 )) break;

s1++; s2++;

}

return ( ! *s1 && ! *s2 ) ? 1 : 0 ;

/* OK : FAIL */

}

/* сравнение строк с игнорированием ANYCHAR */

static strok(register uchar *word, register uchar *pat)

{

while( *word && *pat ){

if( *word == ANYCHAR){

/* Неважно, что есть *pat, но запомним */ vv= *pat;

} else {

if( icase(*pat) != icase(*word) ) break;

}

word++; pat++;

}

/* если слова кончились одновременно ... */ return ( !*word && !*pat) ? 1 : 0;

/* OK : FAIL */

}

/* ЛИШНЯЯ БУКВА */

static int superfluous( uchar *word /* слово для коррекции */ , uchar *s /* эталон */

){

register int i,j,k; int reply;

register len = strlen(word);

for(i=0 ; i < len ; i++){

/* генерим слова , получающиеся удалением одной буквы */ k=0;

for(j=0 ; j < i ; j++) version[k++]=word[j];

for(j=i+1 ; j < len ; j++) version[k++]=word[j];

version[k]='\0';

if( eqi( version, s )) return 1; /* OK */

}

 

return 0;

/* FAIL */

}

А. Богатырёв, 1992-96

- 278 -

Си в UNIX™

/* ПОТЕРЯНА БУКВА */

 

static int hole; /* место,

где вставлена ANYCHAR */

static int lost(uchar *word,

uchar *s)

{

register int i,j,k;

register len = strlen(word);

hole= (-1);

for(i=0 ; i < len+1 ; i++){ k=0;

for(j=0 ; j < i ; j++) version[k++]=word[j];

version[k++]=ANYCHAR; for(j=i ; j < len ; j++)

 

version[k++]=word[j];

version[k]='\0';

 

if( strok( version,

s )){

 

hole=i;

 

 

return 1;

/* OK */

}

 

 

}

 

 

return 0;

/* FAIL */

 

}

/* ИЗМЕНИЛАСЬ ОДНА БУКВА (включает случай ошибки регистра) */ static int changed(uchar *word, uchar *s)

{

register int i,j,k;

register len = strlen(word);

hole = (-1);

for(i=0 ; i < len ; i++){ k=0;

for( j=0 ; j < i ; j++) version[k++]=word[j];

version[k++]=ANYCHAR;

for( j=i+1 ; j < len ; j++) version[k++]=word[j];

version[k]='\0';

if( strok( version,s)){

 

hole=i;

 

 

 

return 1;

/* OK */

}

 

 

 

}

 

 

 

return 0;

/* FAIL

*/

 

}

А. Богатырёв, 1992-96

- 279 -

Си в UNIX™

/* УДВОЕННАЯ БУКВА */

static int duplicates(uchar *word, uchar *s, int leng)

{

register int i,j,k; uchar tmp[80];

if( eqi( word, s )) return 1;

/* OK */

for(i=0;i < leng - 1; i++)

 

/* ищем парные буквы */

 

 

if( word[i]==word[i+1]){

 

 

k=0;

 

 

 

for(j=0 ; j < i ; j++)

 

tmp[k++]=word[j];

 

for(j=i+1 ; j < leng ; j++)

 

tmp[k++]=word[j];

 

tmp[k]='\0';

 

 

if( duplicates( tmp, s, leng-1) == 1)

 

return 1;

/* OK */

}

 

 

 

return 0;

/* FAIL */

 

}

 

 

 

/* ПЕРЕСТАВЛЕНЫ СОСЕДНИЕ БУКВЫ */

 

 

static int swapped(uchar *word, uchar *s)

 

{

 

 

 

register int i,j,k;

 

 

register len = strlen(word);

 

for(i=0;i < len-1;i++){

 

 

k=0;

 

 

for(j=0 ; j < i ; j++)

 

 

version[k++]=word[j];

version[k++]=word[i+1];

 

version[k++]=word[i];

 

for(j=i+2 ; j < len ; j++)

 

 

version[k++]=word[j];

version[k]='\0';

 

 

if( eqi( version,

s))

 

 

return 1;

/* OK */

}

 

 

 

return 0;

/* FAIL */

 

 

}

 

 

 

uchar *words[] = { (uchar *) "bag", (uchar *) "bags", (uchar *) "cook", (uchar *) "cool", (uchar *) "bug", (uchar *) "buy", (uchar *) "cock", NULL

};

#define Bcase(x, operators) case x: { operators; } break;

char *cname[5] = { "переставлены буквы",

"удвоены буквы

",

"потеряна буква

",

"ошибочная буква

",

"лишняя буква

"

};

 

А. Богатырёв, 1992-96

- 280 -

Си в UNIX™

static int spellmatch( uchar *word

/* IN

слово для коррекции */

 

, uchar *words[]

/* IN

таблица допустимых слов */

, uchar **indx

/* OUT ответ */

 

){

 

 

 

int i, code, total = (-1);

 

 

 

uchar **ptr;

 

 

 

if(!*word) return -1;

 

 

 

for(ptr = words; *ptr; ++ptr)

 

 

 

if(eqi(word, *ptr)){

 

 

 

if(indx) *indx = *ptr;

 

 

return 0;

 

 

 

}

 

 

 

/* Нет в таблице, нужен подбор похожих */

 

for(ptr = words; *ptr; ++ptr){

 

 

 

uchar *s = *ptr;

 

 

 

int max = 5;

 

 

 

for(i=0; i < max; i++){

 

 

 

switch( i ){

 

 

 

Bcase(0,code = swapped(word, s)

)

Bcase(1,code = duplicates(word, s, strlen(word)) )

Bcase(2,code = lost(word, s)

)

Bcase(3,code = changed(word, s)

)

Bcase(4,code = superfluous(word, s)

)

}

if(code){

total++;

printf("?\t%s\t%s\n", cname[i], s); if(indx) *indx = s;

/* В случае с дубликатами не рассматривать * на наличие лишних букв */

if(i==1) max = 4;

}

}

}

return total;

}

void main(){

uchar inpbuf[BUFSIZ]; int n;

uchar *reply, **ptr;

setlocale(LC_ALL, ""); for(ptr = words; *ptr; ptr++)

printf("#\t%s\n", *ptr);

do{

printf("> "); fflush(stdout); if(gets((char *)inpbuf) == NULL) break;

switch(spellmatch(inpbuf, words, &reply)){ case -1:

printf("Нет такого слова\n"); break;

case 0:

printf("Слово '%s'\n", reply); break; default:

printf("Неоднозначно\n");

}

} while(1);

}