7.46.
Составьте функцию expand(s1, s2), которая расширяет сокращенные обозначения
вида a-z строки s1 в эквивалентный полный список abcd...xyz в строке s2. Допускаются
сокращения для строчных и прописных букв и цифр. Учтите случаи типа a-b-c, a-z0-9 и
-a-g (соглашение состоит в том, что символ "-", стоящий в начале или в конце, воспринимается буквально).
7.47.
Напишите программу, читающую файл и заменяющую строки вида
|<1 и более пробелов и табуляций><текст>
на пары строк
|.pp
|<текст>
(здесь | обозначает левый край файла, a <> - метасимволы). Это - простейший препроцессор, готовящий текст в формате nroff (это форматтер текстов в UNIX). Усложнения:
....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, весь байт), либо объявить
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" );
}
Какие неприятности ждут нас здесь?
7.51.
Составьте программу, преобразующую текст, состоящий только из строчных букв в
текст, состоящий из прописных и строчных букв. Первая буква и буква после каждой
точки - прописные, остальные - строчные.
слово один. слово два. -->
Слово один. Слово два.
Эта программа может оказаться полезной для преобразования текста, набранного в одном
регистре, в текст, содержащий буквы обоих регистров.
7.52.
Напишите программу, исправляющую опечатки в словах (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);
}
7.53.
Пока я сам писал эту программу, я сделал две ошибки, которые должны быть
весьма характерны для новичков. Про них надо бы говорить раньше, в главе про строки и
в самой первой главе, но тут они пришлись как раз к месту. Вопрос: что печатает следующая программа?
#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.
7.54.
Составьте программу кодировки и раскодировки файлов по заданному ключу (строке
символов).
7.55.
Составьте программу, которая запрашивает анкетные данные типа фамилии, имени,
отчества, даты рождения и формирует файл. Программа должна отлавливать ошибки ввода
несимвольной и нецифровой информации, выхода составляющих даты рождения за допустимые
границы с выдачей сообщений об ошибках. Программа должна давать возможность корректировать вводимые данные. Все данные об одном человеке записываются в одну строку файла
через пробел. Вот возможный пример части диалога (ответы пользователя выделены
жирно):
Введите месяц рождения [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).
7.56.
Составьте программу, осуществляющую выборку информации из файла, сформированного в предыдущей задаче, и ее распечатку в табличном виде. Выборка должна осуществляться по значению любого заданного поля (т.е. вы выбираете поле, задаете его значение и получаете те строки, в которых значение указанного поля совпадает с заказанным
вами значением). Усложнение: используйте функцию сравнения строки с регулярным выражением для выборки по шаблону поля (т.е. отбираются только те строки, в которых значение заданного поля удовлетворяет шаблону). Для чтения файла используйте fscanf,
либо fgets и затем sscanf. Второй способ лучше тем, что позволяет проверить по шаблону значение любого поля - не только текстового, но и числового: так 1234 (строка изображение числа) удовлетворяет шаблону "12*".
7.57.
Составьте вариант программы подсчета служебных слов языка Си, не учитывающий
появление этих слов, заключенных в кавычки.
7.58.
Составьте программу удаления из программы на языке Си всех комментариев. Обратите внимание на особые случаи со строками в кавычках и символьными константами; так
строка
char s[] = "/*";
не является началом комментария! Комментарии записывайте в отдельный файл.
7.59.
Составьте программу выдачи перекрестных ссылок, т.е. программу, которая выводит список всех идентификаторов переменных, используемых в программе, и для каждого
из идентификаторов выводит список номеров строк, в которые он входит.
© Copyright А. Богатырев, 1992-95
Си в UNIX
Назад | Содержание | Вперед