Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
delphi / песни о паскале.pdf
Скачиваний:
66
Добавлен:
26.03.2016
Размер:
5.16 Mб
Скачать

Глава 55 Слова, слова, слова…

Односвязные списки подоспели как раз вовремя, — сейчас они поработают в необычном проекте.

Частотный анализ текста

Однажды разгорелся спор об известном романе «Тихий Дон», — некоторые литераторы усомнились в авторстве Михаила Шолохова. Их сомнения развеяли программисты, вычислившие частотные характеристики нескольких его произведений. Что это за характеристики такие?

Предположим, вы подсчитали, что слово «Паскаль» упомянуто в этой книге 150 раз, а всего в книге 10000 слов. Тогда относительная частота слова «Паскаль» в книге составит 150 / 10000 = 0,015 или 1,5%. Если найти частоту употребления других слов книги, и расположить эти результаты в некотором порядке, то получится картина, подобная отпечатку пальца. У разных авторов эти «отпечатки» разные, зато у одного автора в разных произведениях — очень похожи! Обработав таким частотным анализатором несколько книг Михаила Шолохова, специалисты сравнили результаты и обнаружили на романе «Тихий Дон» «пальчики» донского писателя.

Слово за слово

Итак, мы беремся за разработку слегка упрощенного частотного анализатора. Это опять тот случай, где заранее неизвестен объем обрабатываемых данных. В самом деле, определить приблизительное количество слов в тексте не так уж сложно: посчитаем их на одной странице и умножим на число страниц. Но сколько из этих слов несовпадающих, разных? Не слышу ответа!

Наша программа будет читать не романы, а текстовые файлы, — возьмем файл какой-либо из наших программ, и посчитаем в нём слова, составленные из латинских букв. Для упрощения программы русские слова считать не будем, и пропустим слова, состоящие из одной буквы. Зато примем в расчет слова с цифрами и знаками подчеркивания, например, такие.

Begin, NIL, P1, q2, Words_Count, _1_

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

Структура записи

Накапливать слова будем в списке, а потому разработку программы начнем с конструирования надлежащей записи. Очевидно, что в ней надо предусмотреть

435

Глава 55 Слова, слова, слова

строку для слова и числовое поле для счетчика. Стало быть, структура элемента списка будет такой.

TRec = record

{ Тип записи для подсчета слов }

mWord

: string;

{ Слово из текста – 256 байт }

mCount

: Longint;

{ Счетчик слов – 4 байта }

mNext

: PRec;

{ Указатель на следующий – 4 байта }

end;

 

 

 

 

 

 

 

Сколько памяти займет один такой элемент? Сейчас посчитаем: 256+4+4=264 байта, — не так уж мало! Полагаю, что для слова достаточно и тридцати символов. Но, прежде, чем окончательно выбрать длину строки, открою небольшой секрет, — он касается выделения динамической памяти. Сколько бы памяти ни запросила программа, операционная система выделит кусочек, кратный восьми байтам. То есть, часть байтов в выделяемой порции может быть лишней. Значит, предпочтительный размер записи для динамических переменных кратен восьми байтам. В нашем случае размер записи можно уменьшить до 40 байтов, если объявить её так.

TRec = record

{ Тип записи для подсчета слов }

mWord

: string[31];

{ Слово из текста – 32 байта }

mCount

: Longint;

{ Счетчик слов – 4 байта }

mNext

: PRec;

{ Указатель на следующий – 4 байта }

end;

 

 

 

 

 

 

 

С одной стороны, число 40 кратно 8, а с другой стороны, 31-го символа для слова вполне достаточно.

Алгоритм

Теперь обсудим алгоритм обнаружения и обработки слов. В чем состоит эта обработка? Найдя выделенное слово в списке, нарастим его счетчик — поле mCount, а если слова в списке ещё нет, добавим запись с этим словом и счетчиком, равным единице.

Можно придумать много способов выборки слов из файла. Один из них — построчная обработка, когда каждую строку можно обработать так.

1.Перекодировать все символы строки в верхний регистр.

2.Удалить из начала строки все символы, которые не являются латинской буквой или подчеркиванием, и, если строка стала пустой, то завершить процедуру.

3.Выделить из строки очередное слово и удалить его из строки.

4.Искать слово в списке.

5.Если слово найдено, нарастить его счетчик, а иначе вставить в список запись со счетчиком, равным единице.

436

Глава 55 Слова, слова, слова

6.Прейти к пункту 2.

Вперечисленных действиях нет ничего нового. В самом деле, обработка строк

дело привычное, так же, как поиск в сортированном списке и вставка в него данных. Таким образом, нам остаётся лишь собрать всё это воедино, что и сделано в программе P_55_1.

Процедуры этой программы сходны с аналогичными из полицейской базы данных, их отличает лишь порядок сортировки. Если там сортировка выполнялась по номерам автомобилей, то здесь — по словам.

{ P_55_1 – Частотный анализатор текста }

type

 

 

 

PRec = ^TRec;

{ Тип указатель на запись }

TRec = record

{ Тип записи для подсчета слов }

mWord

: string[31];

{ Слово из текста }

mCount

: Longint;

{ Счетчик слов }

mNext

: PRec;

{ Указатель на следующий элемент }

end;

 

 

 

var List : PRec;

 

{ Указатель на начало списка (голова) }

{ Поиск в сортированном списке } function Find(const aWord: string): PRec; var p: PRec;

begin

p:= List; { Поиск начинаем с головы }

{Двигаемся по списку, пока следующий элемент существует

ислово в нём меньше искомого }

while Assigned(p) and Assigned(p^.mNext) and (p^.mNext^.mWord <= aWord) do p:=p^.mNext;

{ Если конец списка не достигнут и слово совпадает... } if Assigned(p) and (p^.mWord = aWord)

then Find:= p { ... то успешно! } else Find:= nil; { ... а иначе не нашли }

end;

437

Глава 55 Слова, слова, слова

{ Размещение нового элемента в сортированном списке слов } procedure AddToSortList(const aWord : string);

var p, q : PRec; begin

New(p); { Создаем динамическую переменную-запись }

{Размещаем данные в полях записи } p^.mCount:= 1; p^.mWord:= aWord; p^.mNext:=nil;

{Если список пуст... }

if not Assigned(List)

then List:= p { ...голова указывает на первую запись } else begin

q:= List; { Поиск места вставки начинаем с головы }

{ Двигаемся по списку, пока следующий элемент существует

и его номер меньше вставляемого }

while Assigned(q^.mNext) and (q^.mNext^.mWord < aWord)

do q:=q^.mNext;

if q^.mWord > aWord then begin

{ вставка на первое место }

p^.mNext:=List;

{ первый становится вторым }

List:=p;

{ а текущийпервым }

end else begin

 

{ вставка в середине или в конце списка }

p^.mNext:=q^.mNext;

{ связываем текущий со следующим }

q^.mNext:=p;

{ связываем предыдущий с текущим }

end

 

end

end;

{Добавление слова либо увеличение его счетчика } procedure AddWord(const aWord : string);

var P : PRec; begin

P:= Find(aWord); if Assigned(p)

then Inc(P^.mCount)

else AddToSortList(aWord);

end;

{Выделение и добавление слов из прочитанной строки } procedure AddLine(S: string);

const CLetter = ['A'..'Z','_']; CDigits = ['0'..'9'];

var W : string;

i : integer;

438

Глава 55 Слова, слова, слова

begin

{ переводим все буквы строки в верхний регистр } for i:=1 to Length(S) do S[i]:= UpCase(S[i]); while Length(S)>0 do begin

{ удаляем все небуквы в начале строки }

while (Length(S)>0) and not (S[1] in CLetter) do Delete(S,1,1); if Length(S)>0 then begin

W:='';

{ копируем все буквы и цифры в слово W и удаляем из строки } while (Length(S)>0) and (S[1] in CLetter+CDigits) do begin

W:= W+S[1];

Delete(S,1,1);

end;

if Length(W)>1 then AddWord(W); { Если не буква, вставляем в список } end;

end;

end;

{ Распечатка списка } procedure PrintList(var F: text);

var P : PRec; begin

Rewrite(F); P:= List; while Assigned(P) do begin

Writeln(F, P^.mWord, '':(20-Length(P^.mWord)), P^.mCount:5 );

P:= P^.mNext;

end;

Close(F);

end;

var S: string; F: text;

begin {--- Главная программа ---} List:= nil;

Assign(F, 'P_55_1.pas');

Reset(F);

while not Eof(F) do begin Readln(F, S); AddLine(S);

end;

Close(F); Assign(F, 'P_55_1.OUT');

PrintList(F); { Распечатка списка }

end.

439

Соседние файлы в папке delphi