[Pascal] Число повторений слов в тексте

Horita
Сообщения: 14
Зарегистрирован: 06 окт 2008, 11:18

Нужно посчитать число слов. И мне нужна рекурсия :) Ну это фигня.
На чем я застопорилась, это как найти число встречаемости слова в тексте. Посути программа должна пройти каждое слово и выписать сколько раз оно встречается.

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

PROGRAM CountWords(INPUT, OUTPUT);
CONST
  Chars = [' ', '.', ',', '!', '?', ':', ';'];
VAR
  Ch: CHAR;
  T: TEXT;
  S: STRING;
  I, Wc, W: INTEGER;
PROCEDURE Count;
  BEGIN
    WHILE NOT (Ch IN Chars) AND NOT EOF(T)
    DO
      BEGIN
        READ(T, Ch);
        IF Ch IN Chars
        THEN
          BEGIN
            INC(I);
            READ(T, Ch);
            Count;
          END;
      END;
    IF Ch = ' '
    THEN
      BEGIN
        READ(T, Ch);
        Count;
      END;
  END;
BEGIN
  ASSIGN(T, 'Text1.txt');
  RESET(T);
  Count;
  WRITELN(I);
END.
Вот собствено мой подсчет с помощью рекурсии.
Аватара пользователя
Naeel Maqsudov
Сообщения: 2570
Зарегистрирован: 20 фев 2004, 19:17
Откуда: Moscow, Russia
Контактная информация:

Какая же это рекурсия!
Если на вход подать текст "йцу,фыв,ясч,вап,апр" то рекурсивного вызова вообщене произойдет.
А еще можно запросто спровоцировать ситуацию, когда произойдет чтение за концом файла!
1) Тут ошибки
2) Даже при отсутствии ошибок подобный алгоритм для раздельного подсчета слов не годится. Надо все переделывать. Надо где-то (вопрос - где: в массиве? динамическом списке?) запоминать каждое новое слово, и счетчие для него.

На счет применения тут рекурсии я категорически несогласен!
Файл читатся последовательно. Т.е. может быть сколь угодно большим. Если каждое слово это еще один некурсивный вызов, то стек быстро закончится.
Тут надо либо считать, что файл маленький, и тогда грузить его за 1 раз в память и обрабатывать (идиотской рекурсивной функцией, которая даже тут ни разу не нужна), либо считать, что файл большой и следовательно рекурсия тут вообще не применима в принципе!
Horita
Сообщения: 14
Зарегистрирован: 06 окт 2008, 11:18

"йцу,фыв,ясч,вап,апр" при такой строке он насчитает 4 слова, но любой текст заканчивается точкой и если в конце поставить точку то будет 5 слов.
То что подобный способ негодится, раз вы так говорите, наверное так и есть.
Будем думать....
Оффтопом: неподскажите книжки по Си ?
Аватара пользователя
somewhere
Сообщения: 1858
Зарегистрирован: 31 авг 2006, 17:14
Откуда: 71 RUS
Контактная информация:

ИМХО любая рекурсия - великое зло. Здесь подойдет "псевдоалгоритм" предложенный Naeel Maqsudov
Считаем что
TWordStats = record word: string; count : integer; end;
TWords = array of TWordStats;
Words, WordsInLine : TWords;
[1] Если конец файла, то [6]
[2] Читаем строку
[3] Разбиваем на слова (см. раздел "алгоритмы", модифицировать для типов TWords, TWordStats - результат в WordsInline)
[4] Объединение массивов Words - WordsInLine
[4.1] Для каждого слова в WordsInLine выполнить поиск в Words
[4.2] Если найдено - инкремент счетчика, если нет - добавить в конец
[5] Идем к 1
[6] Вывод статистики
It's a long way to the top if you wanna rock'n'roll
Аватара пользователя
Naeel Maqsudov
Сообщения: 2570
Зарегистрирован: 20 фев 2004, 19:17
Откуда: Moscow, Russia
Контактная информация:

Horita писал(а):"йцу,фыв,ясч,вап,апр" при такой строке он насчитает 4 слова


Да. Но рекурсивного вызова при это не произойдет ниодного. Выполните по шагам, если Вам это не очевидно.

Итак для каждого слова нам нужен свой счетчик. Посторяю вопрос: как хотите хранить счетчики? В массиве (но тогда есть ограничение на максимальное количестсво уникальных слов), в динамическом массиве или в связанном списке?
Три варианта...
Horita
Сообщения: 14
Зарегистрирован: 06 окт 2008, 11:18

В динамическом массиве.
Аватара пользователя
Naeel Maqsudov
Сообщения: 2570
Зарегистрирован: 20 фев 2004, 19:17
Откуда: Moscow, Russia
Контактная информация:

Вот правильный подсчет слов с помощью рекурсии:
(Это вместо Вашей первой программы)

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

PROGRAM CountWords(INPUT, OUTPUT);
CONST
  Chars = [' ', '.', ',', '!', '?', ':', ';',#10,#13];
VAR
  T: TEXT;
  Count: integer;

function NextWord:string;
var
  Ch: char;
begin
  Read(T,Ch);
  if (Ch in Chars) or eof(T)
    then NextWord:=''
    else NextWord:=Ch+NextWord;
end;

BEGIN
  Count:=0;
  ASSIGN(T, 'c:\somefile.txt');
  RESET(T);
  repeat
    if NextWord<>'' then inc(Count);
  until eof(T);
  close(T);
  writeln('Найдено ',Count,' слов(а)');
  readln;
END.

Horita
Сообщения: 14
Зарегистрирован: 06 окт 2008, 11:18

Я так поняла, в функции так же можно записывать слово в файл ?
Проходя следующее слово будем сравнивать со словами в файле, если встретится, то слову которое встречается присвоить inc(counter). А если не нашлось, то записать в конец. Так можно сделать ? Щас попробую.
Аватара пользователя
Naeel Maqsudov
Сообщения: 2570
Зарегистрирован: 20 фев 2004, 19:17
Откуда: Moscow, Russia
Контактная информация:

Я сильно извиняюсь, в турбо-паскеле нету же динамических массивов. Поэтому для турбо-паскаля оптимальнее всего использовать все-таки динамический список:

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

PROGRAM CountWords(INPUT, OUTPUT);
CONST
  Chars = [' ', '.', ',', '!', '?', ':', ';',#10,#13];
type
  PCounter=^TCounter;
  TCounter=record
    Word:string;
    Count:integer;
    Next:PCounter;
  end;
VAR
  T: TEXT;
  Counts,Tmp: PCounter;
  W:string;
  CountTotal,CountUnique:integer;

function NextWord:string;
var
  Ch: char;
begin
  Read(T,Ch);
  if (Ch in Chars) or eof(T)
    then NextWord:=''
    else NextWord:=Ch+NextWord;
end;

function AddCounter(var ioHead:PCounter;iWord:string):boolean;
var
  Curr,New:PCounter;
begin
  {$B-}
  Curr:=ioHead;
  AddCounter:=false;
  if Curr<>nil then while (Curr^.Next<>nil) and (Curr^.Word <> iWord) do
    Curr:=Curr^.Next;
  if (Curr=nil) or ((Curr^.Next=nil) and (Curr^.Word<>iWord)) then begin
    getmem(New,SizeOf(TCounter));
    with New^ do begin
      Word:=iWord;
      Count:=1;
      Next:=nil;
      AddCounter:=true;
    end;
    if Curr=nil then ioHead:=New else Curr^.next:=New;
  end else {Curr^.Word=iWord} inc(Curr^.Count);
end;

BEGIN
  Counts:=nil;
  CountTotal:=0;
  CountUnique:=0;
  ASSIGN(T, 'c:\somefile.txt');
  RESET(T);
  repeat
    W:=NextWord;
    if W<>'' then begin
      inc(CountTotal);
      if AddCounter(Counts,W) then inc(CountUnique);
    end;
  until eof(T);
  close(T);

  writeln('Всего слов тексте: ', CountTotal);
  if CountUnique>0 then begin
    writeln('Различных слов обнаружено: ', CountUnique, '. Среди них:');
    Tmp:=Counts;
    while Tmp<>nil do begin
      writeln('Слово "',Tmp^.Word,'" встречается ',Tmp^.Count,'раз(а)');
      Tmp:=Tmp^.Next;
    end;
  end;
  readln;
END.
Если надо для Object Pascal, то можно переделать и заменить списки динамическим массивом.
Аватара пользователя
Naeel Maqsudov
Сообщения: 2570
Зарегистрирован: 20 фев 2004, 19:17
Откуда: Moscow, Russia
Контактная информация:

Horita писал(а):...в функции так же можно записывать слово в файл ?
Проходя следующее слово будем сравнивать со словами в файле, если встретится, то слову которое встречается присвоить inc(counter)...


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

Я бы тогда сделал так:
1) очищаем специальную временную папку
2) проберая по файлу, берем NextWord и а) если в папке есть файл с таким именем, то прочитываем из файла число, прибавляем единицу и записываем обратно в файл (переписывам содержимое файла) б) а если нету такого файла, то создаем его и записываем туда "1".
3) потом сканируем папку и выводим готовые результаты.

Т.е. в качестве хранения счетчиков (а их по одному на каждое слово) можно использовать файловую систему, а не динамический список.
Ответить