Решено: Задача на С.

Модератор: Модераторы разделов

pleer
Сообщения: 31
ОС: Gentoo

Решено: Задача на С.

Сообщение pleer »

Доброго времени суток.
Появилась такая задача.
Есть 2 файла с текстами. Требуется переписать в третий файл предложения из двух данных поочередно. Как только закончится один из файлов - останов. Вроде просто, но как всегда...
С чтением/записью ничего сложного - разберемся. А вот на делении на предложения фантазия иссякла. Пробовал несколько вариантов, но либо получалось неочень либо вообще ничего. Был почти удачный момент с циклом, но он уходил в бесконечность если собрать в Turbo C++ 3.1 (дауж, но раз заказан такой компилятор) а на GCC 4.4.2 относительно нормально.
Вопрос: как таки правильно и не замудрено поделить текст на предложения? Хотябы только по точке или со знаками вопроса и восклицания.
Заранее спасибо.

Вот одна из попыток, которая на мой взгляд самая компактная и удачная из всех испробованых. Если вместо printf("%s\n", strtok( arr, "\n" )); писать просто printf("%s\n", arr); то будет куча лишних "\n" и проблема с предложениями, содержащими этот символ. Но предложения будут искаться более качественно. Ну и неизвестно, какой знак вконце.

Код:

#include <stdio.h> #include <string.h> int main() { FILE *f; char str[255]; char *arr; int i,a; f = fopen ("./test.txt","r"); do { fgets(str,254,f); arr = strtok( str, ".!?" ); while( arr != NULL ) { printf("%s\n", strtok( arr, "\n" )); arr = strtok( NULL, ".!?" ); } } while ((fscanf(f, "%s", str)) != EOF); return 0; }
Спасибо сказали:
Аватара пользователя
Portnov
Модератор
Сообщения: 1786
Статус: Матёрый линуксоид
ОС: Debian testing/unstable

Re: Решено: Задача на С.

Сообщение Portnov »

iУведомление от модератора
Ждём от автора попыток решения. Иначе -
Темы от студентов с запросами готовых решений по заданиям зарываются не глядя.

Работа: Ubuntu 9.10
Дом: Debian testing/unstable и на всякий случай winxp в virtualbox.
Для разнообразия: моя домашняя страница -http://iportnov.ru
Спасибо сказали:
Аватара пользователя
JetMind
Сообщения: 6
ОС: Gentoo, Fedora 11

Re: Решено: Задача на С.

Сообщение JetMind »

Я бы сделал как-то так.

Код: Выделить всё

#include <stdio.h>
#include <stdlib.h>

FILE* f1;
FILE* f2;


void sent(FILE* f) {
    int ch;
    do {
        if (feof(f)) exit(0);
        ch = fgetc(f);
        printf("%c", ch);
    } while (ch != '.' && ch != '!' && ch != '?');
}

void usage() {
    printf("Usage: sentence file1 file2\n");
}

int main(int argc, char* argv[]) {
    if (argc < 3) {
        usage();
        exit(0);
    }

    f1 = fopen(argv[1], "r");
    if (!f1) {
        fprintf(stderr, "Could not open file %s", argv[1]);
        exit(1);
    }

    f2 = fopen(argv[2], "r");
    if (!f1) {
        fprintf(stderr, "Could not open file %s", argv[2]);
        exit(1);
    }

    while (1) {
        sent(f1);
        sent(f2);
    }

    exit(0);
}
Спасибо сказали:
Аватара пользователя
uptime
Сообщения: 1661
Статус: Drinker with computing problems
ОС: kubuntu 8.04

Re: Решено: Задача на С.

Сообщение uptime »

JetMind писал(а):
11.11.2009 22:38
Я бы сделал как-то так.

Код: Выделить всё

...

         if (feof(f)) exit(0);
 ...

программа завершится, как только один из файлов кончится. Это так запроектировано?
The answer, my friend, is blowin' in the wind.
The answer is blowin' in the wind.
Спасибо сказали:
NickLion
Сообщения: 3408
Статус: аватар-невидимка
ОС: openSUSE Tumbleweed x86_64

Re: Решено: Задача на С.

Сообщение NickLion »

Не люблю побайтное чтение, поэтому подкину такое:

Код: Выделить всё

#include <stdio.h>

#define BUF_SIZE 4096
#define BUF2_SIZE 16
#define SENTF__( chars, bs1, bs2 ) "%" #bs1 "[^" chars "]%" #bs2 "[" chars "]"
#define SENTF_( chars, bs1, bs2 ) SENTF__( chars, bs1, bs2 )
#define SENTF( chars ) SENTF_( chars, BUF_SIZE, BUF2_SIZE )

int main(int argc, char *argv[]) {
    char s1[BUF_SIZE], s2[BUF2_SIZE];
    const char* ss = SENTF( ".?!" );
    int r;
    if( argc >= 3 ) {
        FILE* f1 = fopen( argv[1], "rt" );
        FILE* f2 = fopen( argv[2], "rt" );
        if( f1 && f2 ) {
            while( !( feof( f1 ) || feof( f2 ) ) ) {
                while( ( r = fscanf( f1, ss, s1, s2 ) ) == 1 ) printf( "%.*s", BUF_SIZE, s1 );
                if( r == 2 ) printf( "%s%s", s1, s2 );
                while( ( r = fscanf( f2, ss, s1, s2 ) ) == 1 ) printf( "%.*s", BUF_SIZE, s1 );
                if( r == 2 ) printf( "%s%s", s1, s2 );
            }
        } else {
            fprintf( stderr, "Oops!\n" );
        }
    } else {
        fprintf( stderr, "Oops!\n" );
    }
    return 0;
}

Выглядит страшненько, знаю :)
Правда не решал проблему пробелов между первыми предложениями... вроде достаточно дописать "]" заменить на " \t\n]" во втором множестве... но проверять уже лень.
Спасибо сказали:
Аватара пользователя
Lyset
Сообщения: 107
ОС: Ubuntu

Re: Решено: Задача на С.

Сообщение Lyset »

pleer писал(а):
11.11.2009 21:41
Вопрос: как таки правильно и не замудрено поделить текст на предложения? Хотябы только по точке или со знаками вопроса и восклицания.

Как выглядят границы предложений? Вопрос/банг/точка, после них любое кол-во вайтспейсов (пробелов/табов/переносов), затем заглавная буква. Естественно, это прокатит лишь для некоторых европейских языков.
Спасибо сказали:
Аватара пользователя
JetMind
Сообщения: 6
ОС: Gentoo, Fedora 11

Re: Решено: Задача на С.

Сообщение JetMind »

uptime писал(а):
12.11.2009 00:06
JetMind писал(а):
11.11.2009 22:38
Я бы сделал как-то так.

Код: Выделить всё

...

         if (feof(f)) exit(0);
 ...

программа завершится, как только один из файлов кончится. Это так запроектировано?

Именно так. По крайней мере так требуется по заданию, либо я не правильно понял топикстартера.
Спасибо сказали:
pleer
Сообщения: 31
ОС: Gentoo

Re: Решено: Задача на С.

Сообщение pleer »

JetMind писал(а):
12.11.2009 02:53
uptime писал(а):
12.11.2009 00:06
JetMind писал(а):
11.11.2009 22:38
Я бы сделал как-то так.

Код: Выделить всё

...

         if (feof(f)) exit(0);
 ...

программа завершится, как только один из файлов кончится. Это так запроектировано?

Именно так. По крайней мере так требуется по заданию, либо я не правильно понял топикстартера.

Да, так и должно быть. Поразбирался в примере и подогнал под задачу. Вот что вышло

Код:

#include <stdio.h> #include <stdlib.h> #include <string.h> FILE *input1, *input2; FILE *output; void sent(FILE* f) { int ch,pos = 0; do { if (feof(f)) exit(0); ch = fgetc(f); if (ch >= 0) if (ch == 10 && pos != 0) fputc(32,output); else if (ch == 10 || ch == 9); else if (ch == 32 && pos == 0); else fputc(ch,output); pos++; } while (ch != '.' && ch != '!' && ch != '?'); fputs("\n",output); } int main() { input1 = fopen( "./F", "r"); if (!input1) { fprintf(stderr, "Could not open file F."); return 1; } input2 = fopen( "./G", "r"); if (!input2) { fprintf(stderr, "Could not open file G."); return 1; } output = fopen( "./Q", "w"); if (!output) { fprintf(stderr, "Could not open file Q."); return 1; } while (1) { sent(input1); sent(input2); } return 0; }


NickLion писал(а):
12.11.2009 01:19
Не люблю побайтное чтение, поэтому подкину такое:
...
Выглядит страшненько, знаю :)
Правда не решал проблему пробелов между первыми предложениями... вроде достаточно дописать "]" заменить на " \t\n]" во втором множестве... но проверять уже лень.

Интересно. Только я не совсем понимаю что творится в

Код:

while( ( r = fscanf( f1, ss, s1, s2 ) ) == 1 ) printf( "%.*s", BUF_SIZE, s1 );

и в

Код:

#define SENTF__( chars, bs1, bs2 ) "%" #bs1 "[^" chars "]%" #bs2 "[" chars "]" #define SENTF_( chars, bs1, bs2 ) SENTF__( chars, bs1, bs2 ) #define SENTF( chars ) SENTF_( chars, BUF_SIZE, BUF2_SIZE )

Не разъясните?

Lyset писал(а):
12.11.2009 02:39
Как выглядят границы предложений? Вопрос/банг/точка, после них любое кол-во вайтспейсов (пробелов/табов/переносов), затем заглавная буква. Естественно, это прокатит лишь для некоторых европейских языков.

Ну образца нет. Предположительно обычный текст на английском или русском в CP1251. Значит и границы предложений будут как приведенные Вами. Задача была не одна, а судя по их уровню сверхестественного анализа не требуется.
Попробую выбить пример.
Спасибо сказали:
NickLion
Сообщения: 3408
Статус: аватар-невидимка
ОС: openSUSE Tumbleweed x86_64

Re: Решено: Задача на С.

Сообщение NickLion »

pleer писал(а):
12.11.2009 17:09
Интересно. Только я не совсем понимаю что творится в
...
Не разъясните?

Код:

#define SENTF__( chars, bs1, bs2 ) "%" #bs1 "[^" chars "]%" #bs2 "[" chars "]" #define SENTF_( chars, bs1, bs2 ) SENTF__( chars, bs1, bs2 ) #define SENTF( chars ) SENTF_( chars, BUF_SIZE, BUF2_SIZE )

Это макросы для формирования строки вида "%4096[^.?!]%16[.?!]", но не зашить просто так, а чтобы эта строка зависела от реальных размеров буферов.
Последний макрос - собственно для использования, промежуточный - для раскрытия макросов BUF_SIZE/BUF2_SIZE (иначе получается "%BUF_SIZE[^.?!]%BUF2_SIZE[.?!]"), а первый - это собственно задание строки - "%" "4096" "[^" ".?!" "]%" "16" "[" ".?!" "]" - что сливается в одну строку. А эта строка и описывает ввод 2 строк - первая - не более BUF_SIZE символов, состоит из любых символов, кроме ".?!", а вторая - не более 16 и состоит только из ".?!".

Код:

while( ( r = fscanf( f1, ss, s1, s2 ) ) == 1 ) printf( "%.*s", BUF_SIZE, s1 );

А это проверка - хватило ли первого буфера для предложения. Если не хватило - будет введено только одно поле, его выведем и снова сделаем ввод того же самого.
Забыл, кстати, аналогичную проверку для второго буфера сделать, надо бы добавить. Но тогда будет проще разорвать на 2 отдельных ввода.
Спасибо сказали:
pleer
Сообщения: 31
ОС: Gentoo

Re: Решено: Задача на С.

Сообщение pleer »

NickLion писал(а):
13.11.2009 11:22
Это макросы для формирования строки вида "%4096[^.?!]%16[.?!]", но не зашить просто так, а чтобы эта строка зависела от реальных размеров буферов.

А, значит это чтото вроде маски для файла (потока). И проверяется за раз части размером максимум с BUF_SIZE. А fscan пишет все в s1 до нужного символа, который идет в s2. Ну и на вывод идет только s1 если прочитано уже BUF_SIZE байт и s1 s2 вместе если таки встретились нужные знаки. Я правильно понял?
Еще вопрос: а как при таком подходе победить наличие в предложениях перехода на новую строку (вывод получается тоже многострочным) и знаки табуляции перед пред самим предложением? Или что можно почитать на эту тему, предпочтительно, на русском?
Спасибо сказали:
NickLion
Сообщения: 3408
Статус: аватар-невидимка
ОС: openSUSE Tumbleweed x86_64

Re: Решено: Задача на С.

Сообщение NickLion »

pleer писал(а):
13.11.2009 17:26
А, значит это чтото вроде маски для файла (потока). И проверяется за раз части размером максимум с BUF_SIZE. А fscan пишет все в s1 до нужного символа, который идет в s2. Ну и на вывод идет только s1 если прочитано уже BUF_SIZE байт и s1 s2 вместе если таки встретились нужные знаки. Я правильно понял?

Да, вроде, правильно :)

pleer писал(а):
13.11.2009 17:26
Еще вопрос: а как при таком подходе победить наличие в предложениях перехода на новую строку (вывод получается тоже многострочным) и знаки табуляции перед пред самим предложением? Или что можно почитать на эту тему, предпочтительно, на русском?

Табуляции перед предложением победить просто - добавить их считывание в "пустоту" - "%*[\t]".Но это именно перед предложением. Внутри такое не получится (вроде). Как и пропустить переводы строк. Так что, боюсь, придется все-равно посимвольно анализировать :(
Спасибо сказали:
Аватара пользователя
drBatty
Сообщения: 8735
Статус: GPG ID: 4DFBD1D6 дом горит, козёл не видит...
ОС: Slackware-current

Re: Решено: Задача на С.

Сообщение drBatty »

pleer писал(а):
11.11.2009 21:41
Есть 2 файла с текстами. Требуется переписать в третий файл предложения из двух данных поочередно. Как только закончится один из файлов - останов. Вроде просто, но как всегда...

это ближе к регулярным выражениям на sed. ИМХО.
http://emulek.blogspot.ru/ Windows Must Die
Учебник по sed зеркало в github

Скоро придёт
Осень
Спасибо сказали:
pleer
Сообщения: 31
ОС: Gentoo

Re: Решено: Задача на С.

Сообщение pleer »

NickLion писал(а):
14.11.2009 06:47
Табуляции перед предложением победить просто - добавить их считывание в "пустоту" - "%*[\t]".Но это именно перед предложением. Внутри такое не получится (вроде). Как и пропустить переводы строк. Так что, боюсь, придется все-равно посимвольно анализировать :(

Хм. Нечто подобное я пробовал. Программа при этом выводит только первые предложение с символом "\t" в начале. Если найдет предложения другого вида - уходит в бесконечный цикл. Получившаяся строчка задания маски выглядит вот так. Остального кода не менял.

Код:

#define SENTF__( chars, bs1, bs2 ) "%*[\t]%" #bs1 "[^" chars "]%" #bs2 "[" chars "]%*[ ]"
Но, самое интересное в правильности (или нет?) кода. Данная неисправность справедлива только для GCC. Пробовал версии GCC 4.4.2, 4.3.2 и MinGW 3.4.5. Тем не менее, на Turbo C++ 3.1, 4.5 и C++ builder 6 все работало нормально. Хотел еще опробовать на icc (ну и посмотреть - никогда не видел), но 700Мб и сборка(Gentoo).... не сегодня.
В чем секрет такого поведения GCC?

drBatty писал(а):
14.11.2009 12:41
это ближе к регулярным выражениям на sed. ИМХО.

То есть, Вы предлогаете использовать sed? sed, кончено, хорошо, но задача есть задача. Темболее программа должна работать и на враждебной системе. Или я не так понял, и Вы имеете в виду другое?
Спасибо сказали:
Аватара пользователя
/dev/random
Администратор
Сообщения: 5412
ОС: Gentoo

Re: Решено: Задача на С.

Сообщение /dev/random »

pleer писал(а):
14.11.2009 21:50
Хм. Нечто подобное я пробовал. Программа при этом выводит только первые предложение с символом "\t" в начале. Если найдет предложения другого вида - уходит в бесконечный цикл. Получившаяся строчка задания маски выглядит вот так. Остального кода не менял.

Код:

#define SENTF__( chars, bs1, bs2 ) "%*[\t]%" #bs1 "[^" chars "]%" #bs2 "[" chars "]%*[ ]"
Но, самое интересное в правильности (или нет?) кода. Данная неисправность справедлива только для GCC. Пробовал версии GCC 4.4.2, 4.3.2 и MinGW 3.4.5. Тем не менее, на Turbo C++ 3.1, 4.5 и C++ builder 6 все работало нормально. Хотел еще опробовать на icc (ну и посмотреть - никогда не видел), но 700Мб и сборка(Gentoo).... не сегодня.
В чем секрет такого поведения GCC?

(man scanf) писал(а):[ Matches a non-empty sequence of characters from the specified set of accepted characters;
(выделено мной)
Если табов нет, то последовательность уже не будет непустой, чтение данного элемента строки формата провалится, и scanf прервёт своё выполнение, не считывая больше ничего. Это абсолютно естественное поведение, и весьма странно, что builder ведёт себя по-другому.
Чтобы избежать проблемы, scanf нужно разделить на 2:
scanf("%*[\t]");
scanf("остальное");
В этом случае "провалится" лишь первый scanf, а второй спокойно продолжит работу.
Спасибо сказали:
Аватара пользователя
drBatty
Сообщения: 8735
Статус: GPG ID: 4DFBD1D6 дом горит, козёл не видит...
ОС: Slackware-current

Re: Решено: Задача на С.

Сообщение drBatty »

pleer писал(а):
14.11.2009 21:50
То есть, Вы предлогаете использовать sed? sed, кончено, хорошо, но задача есть задача. Темболее программа должна работать и на враждебной системе. Или я не так понял, и Вы имеете в виду другое?

sed прелесно работает везде. и на маках и на виндовс.
http://emulek.blogspot.ru/ Windows Must Die
Учебник по sed зеркало в github

Скоро придёт
Осень
Спасибо сказали:
pleer
Сообщения: 31
ОС: Gentoo

Re: Решено: Задача на С.

Сообщение pleer »

/dev/random писал(а):
15.11.2009 06:54
[ Matches a non-empty sequence of characters from the specified set of accepted characters;
(выделено мной)
Если табов нет, то последовательность уже не будет непустой, чтение данного элемента строки формата провалится, и scanf прервёт своё выполнение, не считывая больше ничего. Это абсолютно естественное поведение, и весьма странно, что builder ведёт себя по-другому.
Чтобы избежать проблемы, scanf нужно разделить на 2:
scanf("%*[\t]");
scanf("остальное");
В этом случае "провалится" лишь первый scanf, а второй спокойно продолжит работу.

Интересно. Опробовал еще на tcc, icc и Digital Mars - все также. Значит таки борланд не прав. Исправил код - обошлось без неожиданностей, собирается и работает везде нормально. Спасибо за подсказку.

писал(а):
15.11.2009 20:20
sed прелесно работает везде. и на маках и на виндовс.

Ну есть он или нет - программу писать таки прийдется.

Заинтересовал еще и такой вопрос. Можно ли при таком подходе (или хотя бы побайтово) одолеть предложения типа "Я И. И. Иванов."? Ничего в голову не приходит. Или не стоит и это уже слишком для такой задаки?
Спасибо сказали:
Аватара пользователя
/dev/random
Администратор
Сообщения: 5412
ОС: Gentoo

Re: Решено: Задача на С.

Сообщение /dev/random »

pleer писал(а):
16.11.2009 12:07
Заинтересовал еще и такой вопрос. Можно ли при таком подходе (или хотя бы побайтово) одолеть предложения типа "Я И. И. Иванов."? Ничего в голову не приходит. Или не стоит и это уже слишком для такой задаки?

В теории - можно. Нужны переменные, которые будут хранить 2 символа, прошедших перед текущим (при входе инициализируются пробелами). Если там пробел и заглавная буква, то продолжаем работу даже если наткнулись на точку. Но учтите, что вам придётся при проверке учитывать возможность существования различных кодировок. Чтобы избежать этого, придётся перейти на wchar.
Спасибо сказали:
pleer
Сообщения: 31
ОС: Gentoo

Re: Решено: Задача на С.

Сообщение pleer »

/dev/random писал(а):
16.11.2009 12:23
В теории - можно. Нужны переменные, которые будут хранить 2 символа, прошедших перед текущим (при входе инициализируются пробелами). Если там пробел и заглавная буква, то продолжаем работу даже если наткнулись на точку. Но учтите, что вам придётся при проверке учитывать возможность существования различных кодировок. Чтобы избежать этого, придётся перейти на wchar.

Да, вроде должно в теории. Но такой подход все равно должен поделить приведенный мною пример на несколько предложений. Ведь там же есть и знак и пробел и буква заглавная.
Спасибо сказали:
Аватара пользователя
/dev/random
Администратор
Сообщения: 5412
ОС: Gentoo

Re: Решено: Задача на С.

Сообщение /dev/random »

pleer писал(а):
16.11.2009 16:58
Да, вроде должно в теории. Но такой подход все равно должен поделить приведенный мною пример на несколько предложений. Ведь там же есть и знак и пробел и буква заглавная.

Наоборот. Если пробел и заглавная буква _перед_ точкой, то точка должна _игнорироваться_.
Спасибо сказали:
pleer
Сообщения: 31
ОС: Gentoo

Re: Решено: Задача на С.

Сообщение pleer »

/dev/random писал(а):
16.11.2009 17:07
Наоборот. Если пробел и заглавная буква _перед_ точкой, то точка должна _игнорироваться_.

Ааа. А тогда как быть с предложениями, содержащими "... , т.к. ..." и подобное? Подобрать конечно можно, но комбинаций то не мало. Я думал, что есть способ универсальный или около того. Видимо прийдется припаивать такие проверки.
Спасибо сказали:
Аватара пользователя
/dev/random
Администратор
Сообщения: 5412
ОС: Gentoo

Re: Решено: Задача на С.

Сообщение /dev/random »

pleer писал(а):
16.11.2009 17:25
Я думал, что есть способ универсальный или около того.

Универсальный способ есть в США - там принято в конце предложения ставить не один, а 2 пробела. У нас универсального способа нет.
Спасибо сказали:
Аватара пользователя
drBatty
Сообщения: 8735
Статус: GPG ID: 4DFBD1D6 дом горит, козёл не видит...
ОС: Slackware-current

Re: Решено: Задача на С.

Сообщение drBatty »

/dev/random писал(а):
16.11.2009 17:30
Универсальный способ есть в США - там принято в конце предложения ставить не один, а 2 пробела. У нас универсального способа нет.
я теперь всегда так делаю, если бы все так-же поступали... К тому-же, на форумах это съедается :(
http://emulek.blogspot.ru/ Windows Must Die
Учебник по sed зеркало в github

Скоро придёт
Осень
Спасибо сказали:
pleer
Сообщения: 31
ОС: Gentoo

Re: Решено: Задача на С.

Сообщение pleer »

Понятно. Значит будем пилить под каждые неустраивающие случаи.
Ну а тему можно закрывать, раз решение есть. Всем спасибо за помощь и пояснения.
Спасибо сказали: