Чтение онлайн

ЖАНРЫ

C++. Сборник рецептов

Когсуэлл Джефф

Шрифт:

Методика использования потоков в примере 4.26 почти идентична той, которая описана в рецептах 4.14 и 4.15, но несколько проще, так как он только исследует файл, не внося никаких изменений.

Смотри также

Рецепты 4.14 и 4.15.

4.18. Подсчет вхождений каждого слова в текстовом файле

Проблема

Требуется подсчитать количество вхождений в текстовом файле каждого слова.

Решение

Для чтения из текстового файла непрерывных фрагментов текста используйте

operator>>
, определенный в
<string>
,
а для сохранения каждого слова и его частоты в файле используйте
map
, определенный в
<map>
. Пример 4.27 демонстрирует, как это делается.

Пример 4.27. Подсчет частоты слов

1 #include <iostream>

2 #include <fstream>

3 #include <map>

4 #include <string>

5

6 typedef std::map<std::string, int> StrIntMap;

7

8 void countWords(std::istream& in, StrIntMap& words) {

9

10 std::string s;

11

12 while (in >> s) {

13 ++words[s];

14 }

15 }

16

17 int main(int argc, char** argv) {

18

19 if (argc < 2)

20 return(EXIT_FAILURE);

21

22 std::ifstream in(argv[1]);

23

24 if (!in)

25 exit(EXIT_FAILURE);

26

27 StrIntMap w;

28 countWords(in, w);

29

30 for (StrIntMap::iterator p = w.begin;

31 p != w.end; ++p) {

32 std::cout << p->first << " присутствует "

33 << p->second << " раз.\n";

34 }

35 }

Обсуждение

Пример 4.27 кажется вполне простым, но в нем делается больше, чем кажется. Большая часть тонкостей связана с

map
, так что вначале давайте обсудим его.

Если вы не знакомы с

map
, то вам стоит узнать про него,
map
 — это шаблон класса контейнера, который является частью STL. Он хранит пары ключ-значение в порядке, определяемом
std::less
или вашей собственной функцией сравнения. Типы ключей и значений, которые можно хранить в нем, зависят только от вашего воображения. В этом примере мы просто сохраняем
string
и
int
.

В строке 6 я для упрощения читаемости кода использовал

typedef
.

typedef map<string, int> StrIntMap;

Таким образом,

StrIntMap
— это
map
, который хранит пары string/int. Каждая
string
это уникальное слово именно по этой причине я использую ее как ключ, — которое было прочитано из входного потока, а связанное с ней
int
— это число раз, которое это слово встретилось. Все, что осталось, — это прочитать все слова по одному, добавить их в map, если их там еще нет, и увеличить значение счетчика, если они там уже есть.

Это делает

countWords
. Основная логика кратка.

while (in >> s) {

 ++words[s];

}

operator>>
читает из левого операнда (
istream
) непрерывные отрезки, не содержащие пробелов, и помещает их в правый операнд (
string
). После прочтения слова все, что требуется сделать, — это обновить статистику в
map
, и это делается в следующей строке.

++words[s];

map
определяет
operator[]
, позволяющий получить значение данного ключа (на самом деле он возвращает ссылку на само значение), так что для его инкремента просто инкрементируется значение, индексируемое с помощью заданного ключа. Но здесь могут возникнуть небольшие осложнения. Что, если ключа в map еще нет? Разве мы не попытаемся увеличить несуществующий элемент, и не обрушится ли программа, как в случае с обычным массивом? Нет,
map
определяет
operator[]
не так, как другие контейнеры STL или обычные массивы.

В

map operator[]
делает две вещи: если ключ еще не существует, он создает значение, используя конструктор типа значения по умолчанию, и добавляет в
map
эту новую пару ключ/значение, а если ключ уже существует, то никаких изменений не вносится. В обоих случаях возвращается ссылка на значение, определяемое ключом, даже если это значение было только что создано конструктором по умолчанию. Это удобная возможность (если вы знаете о ее существовании), так как он устраняет необходимость проверки в клиентском коде существования ключа перед его добавлением.

Теперь посмотрите на строки 32 и 33. Итератор указывает на члены, которые называются

first
и
second
— что это такое?
map
обманывает вас, используя для хранения пар имя/значение другой шаблон класса: шаблон класса
pair
, определенный в
<utility>
(уже включенный в
<map>
). При переборе элементов, хранящихся в
map
, вы получите ссылки на объекты
pair
. Работа с
pair
проста. Первый элемент пары хранится в элементе
first
, а второй хранится, естественно, в
second
.

В примере 4.27 я для чтения из входного потока непрерывных фрагментов текста использую

operator>>
, что отличается от некоторых других примеров. Я делаю это для демонстрации того, как это делается, но вам почти наверняка потребуется изменить его поведение в зависимости от определения «слова» текстового файла. Например, рассмотрим фрагмент вывода, генерируемого примером 4.27.

with присутствует 5 раз.

work присутствует 3 раз.

workers присутствует 3 раз.

Поделиться с друзьями: