Составьте функцию expand(s1, s2), которая расширяет сокращенные обозначения вида a-z строки s1 в эквивалентный полный список abcd...xyz в строке s2. Допускаются сокращения для строчных и прописных букв и цифр. Учтите случаи типа a-b-c, a-z0-9 и -a-g (соглашение состоит в том, что символ "-", стоящий в начале или в конце, воспринимается буквально).
Напишите программу, читающую файл и заменяющую строки вида
|<1 и более пробелов и табуляций><текст>
на пары строк
|.pp |<текст>
(здесь | обозначает левый край файла, a <> - метасимволы). Это - простейший препроцессор, готовящий текст в формате nroff (это форматтер текстов в UNIX). Усложнения:
\&<текст, начинающийся с точки или '>
.ip <число> <текст>
....xxxx начало- => ....xxxx началоконец конец yyyy...... yyyy................
Вызывайте этот препроцессор разметки текста так:
$ prep файлы... | nroff -me > text.lp
Составьте программу преобразования прописных букв из файла ввода в строчные, используя при этом функцию, в которой необходимо организовать анализ символа (действительно ли это буква). Строчные буквы выдавать без изменения. Указание: используйте макросы из <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 должны пониматься только буквы (увы, не во всех реализациях это так!).
Обратите внимание, что если мы выделяем класс символов при помощи сравнения, например:
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, весь байт), либо объявить
unsigned char ch;
что означает, что при приведении к int знаковый бит не расширяется.
Рассмотрим еще один пример:
main(){ char ch; /* 0377 - код последнего символа алфавита ASCII */ for (ch = 0100; ch <= 0377; ch++ ) printf( "%03o %s\n", ch & 0377, ch >= 0300 && ch < 0340 ? "yes" : "no" ); }
Какие неприятности ждут нас здесь?
(ch & 0377) или ((unsigned) ch)
Составьте программу, преобразующую текст, состоящий только из строчных букв в текст, состоящий из прописных и строчных букв. Первая буква и буква после каждой точки - прописные, остальные - строчные.
слово один. слово два. --> Слово один. Слово два.
Эта программа может оказаться полезной для преобразования текста, набранного в одном регистре, в текст, содержащий буквы обоих регистров.
Напишите программу, исправляющую опечатки в словах (spell check): программе задан список слов; она проверяет - является ли введенное вами слово словом из списка. Если нет - пытается найти наиболее похожее слово из списка, причем если есть несколько похожих - выдает все варианты. Отлавливайте случаи:
Надо проверять каждую букву слова. Возможно вам будет удобно использовать рекурсию. Подсказка: для некоторых проверок вам может помочь функция match:
слово_таблицы = "дом"; if(strlen(входное_слово) <= strlen(слово_таблицы)+1 && match(входное_слово, "*д*о*м*") ... /* похоже */ *о*м* ?дом дом? *д*м* д?ом *д*о* до?м
Приведем вариант решения этой задачи:
#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 */ } /* ПОТЕРЯНА БУКВА */ 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 */ } /* УДВОЕННАЯ БУКВА */ 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] = { "переставлены буквы", "удвоены буквы ", "потеряна буква ", "ошибочная буква ", "лишняя буква " }; 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); }
Пока я сам писал эту программу, я сделал две ошибки, которые должны быть весьма характерны для новичков. Про них надо бы говорить раньше, в главе про строки и в самой первой главе, но тут они пришлись как раз к месту. Вопрос: что печатает следующая программа?
#include <stdio.h> char *strings[] = { "Первая строка" "Вторая строка" "Третяя строка", "Четвертая строка", NULL }; void main(){ char **p; for(p=strings;*p;++p) printf("%s\n", *p); }
А печатает она вот что:
Первая строкаВторая строкаТретяя строка Четвертая строка
Дело в том, что ANSI компилятор Си склеивает строки:
"начало строки" "и ее конец"
если они разделены пробелами в смысле isspace, в том числе и пустыми строками. А в нашем объявлении массива строк strings мы потеряли несколько разделительных запятых!
Вторая ошибка касается того, что можно забыть поставить слово break в операторе switch, и долго после этого гадать о непредсказуемом поведении любого поступающего на вход значения. Дело просто: пробегаются все случаи, управление проваливается из case в следующий case, и так много раз подряд! Это и есть причина того, что в предыдущем примере все case оформлены нетривиальным макросом Bcase.
Составьте программу кодировки и раскодировки файлов по заданному ключу (строке символов).
Составьте программу, которая запрашивает анкетные данные типа фамилии, имени, отчества, даты рождения и формирует файл. Программа должна отлавливать ошибки ввода несимвольной и нецифровой информации, выхода составляющих даты рождения за допустимые границы с выдачей сообщений об ошибках. Программа должна давать возможность корректировать вводимые данные. Все данные об одном человеке записываются в одну строку файла через пробел. Вот возможный пример части диалога (ответы пользователя выделены жирно):
Введите месяц рождения [1-12]: 14 <ENTER> *** Неправильный номер месяца (14). Введите месяц рождения [1-12]: март <ENTER> *** Номер месяца содержит букву 'м'. Введите месяц рождения [1-12]: <ENTER> Вы хотите закончить ввод ? n Введите месяц рождения [1-12]: 11 <ENTER> Ноябрь Введите дату рождения [1-30]: _
В таких программах обычно ответ пользователя вводится как строка:
printf("Введите месяц рождения [1-12]: "); fflush(stdout); gets(input_string);
затем (если надо) отбрасываются лишние пробелы в начале и в конце строки, затем введенный текст input_string анализируется на допустимость символов (нет ли в нем не цифр?), затем строка преобразуется к нужному типу (например, при помощи функции atoi переводится в целое) и проверяется допустимость полученного значения, и.т.д.
Вводимую информацию сначала заносите в структуру; затем записывайте содержимое полей структуры в файл в текстовом виде (используйте функцию fprintf, а не fwrite).
Составьте программу, осуществляющую выборку информации из файла, сформированного в предыдущей задаче, и ее распечатку в табличном виде. Выборка должна осуществляться по значению любого заданного поля (т.е. вы выбираете поле, задаете его значение и получаете те строки, в которых значение указанного поля совпадает с заказанным вами значением). Усложнение: используйте функцию сравнения строки с регулярным выражением для выборки по шаблону поля (т.е. отбираются только те строки, в которых значение заданного поля удовлетворяет шаблону). Для чтения файла используйте fscanf, либо fgets и затем sscanf. Второй способ лучше тем, что позволяет проверить по шаблону значение любого поля - не только текстового, но и числового: так 1234 (строка изображение числа) удовлетворяет шаблону "12*".
Составьте вариант программы подсчета служебных слов языка Си, не учитывающий появление этих слов, заключенных в кавычки.
Составьте программу удаления из программы на языке Си всех комментариев. Обратите внимание на особые случаи со строками в кавычках и символьными константами; так строка
char s[] = "/*";
не является началом комментария! Комментарии записывайте в отдельный файл.
Составьте программу выдачи перекрестных ссылок, т.е. программу, которая выводит список всех идентификаторов переменных, используемых в программе, и для каждого из идентификаторов выводит список номеров строк, в которые он входит.
© Copyright А. Богатырев, 1992-95
Си в UNIX
Назад | Содержание | Вперед