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

Назад | Содержание | Вперед