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). Усложнения:

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

    
                 \&<текст, начинающийся с точки или '>
    
    
  • строки, начинающиеся с цифры, заменять на
    
                 .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, весь байт), либо объявить


            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(входное_слово, "*д*о*м*") ... /* похоже */

            *о*м*           ?дом         дом?

            *д*м*           д?ом

            *д*о*           до?м

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


    #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.

Составьте программу выдачи перекрестных ссылок, т.е. программу, которая выводит список всех идентификаторов переменных, используемых в программе, и для каждого из идентификаторов выводит список номеров строк, в которые он входит.

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

Используются технологии uCoz