Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

2 Курс Информатика VBA(ЗО) / Книги / В.Д.Хорев - Самоучитель программирования на VBA в Microsoft Office

.pdf
Скачиваний:
2702
Добавлен:
31.05.2015
Размер:
21.66 Mб
Скачать

Частотный словарь 113

Const MaxWords = 3000

Const WordsInDictionary = 200

см. также в приложении раздел “Константы”.

Затем нужно объявить два массива: в одном из них (Words) будут храниться сами подчитываемые слова, а второй (WordFrq) будет содержать соответствующие этим словам частоты:

Dim Words(0 To MaxWords) As String

Dim WordFrq(0 To MaxWords) As Integer

В результате будут созданы два массива размером 3001 элемент каждый (3001, потому что отсчет начат с 0): один для хранения строковых значений типа String, и второй — для целых чисел

типа Integer. Кроме того, потребуются другие переменные различных типов. Каждая из них будем упоминаться по мере необходимости.

см. также в приложении раздел “Массивы”.

Прежде всего, инициализируем массивы: при очередном запуске макроса каждый элемент массива Words должен содержать пустую строку, а каждый элемент WordFrq — число 0.

For I = 0 To MaxWords

Words(I) = ""

WordFrq(I) = 0

Next I

Теперь необходимо перебрать все слова документа и выполнить соответствующие подсчеты. Как просмотреть документ слово за словом, уже было рассмотрено.

см. в гл. 3 раздел “Макрос-архивариус анализирует содержимое Word-документа”.

Это без труда можно осуществить, используя обычный цикл For, и обращаясь к элементам семейства ActiveDocument.Words по номерам. Но, вообще говоря, в языке программирования Visual Basic для приложений имеется специальная конструкция For Each, предназначенная для перебора всех элементов какого-либо семейства. Если объявить переменную типа Object (назовем ее MyWord), то при помощи приведенного ниже оператора можно последовательно получить в эту переменную все слова документа:

For Each MyWord In ActiveDocument.Words

...

Next MyWord

Внутри этой конструкции при каждом проходе цикла переменная MyWord представляет текстовый диапазон (Range-объект), заключающий в себе очередное слово.

см. также в приложении разделы “Объектные переменные” и “Цикл For Each … Next”.

Как уже отмечалось, под словом MS Word и VBA могут понимать как слово в обычном смысле, так и знак препинания или непечатаемый символ абзаца. Чтобы отсеять все эти “слова”, а также, чтобы не включать в рассмотрение слова из одной или двух букв, наложим условие: “только для слов, состоящих из более чем двух символов” (напомним, строковая функция Trim удаляет пробелы перед и после слова, а функция Len возвращает длину строки, переданной ей в параметре):

For Each MyWord In ActiveDocument.Words If Len(Trim(MyWord)) > 2 Then

...

End If Next MyWord

114 Глава 4. Практикум программирования на VBA для Excel и Word

см. также в приложении разделы “Функции удаления пробелов (LTrim, RTrim, Trim)” и “Форматирование строки”.

Теперь можно считать, что “скелет” конструкции макроса готов. Остается создать программный код, который непосредственно подсчитывает частоту слов в документе.

Для этого потребуются две переменные. Целочисленная переменная WordCount будет играть роль “счетчика слов”, указывающего на первую свободную позицию в массиве Words (понятно, что вначале массив пуст и переменная WordCount указывает на первый его элемент). Затем, по мере заполнения массива встреченными словами, WordCount будет отодвигать границу занятой части массива все дальше и дальше. При рассмотрении каждого очередного слова в документе предполагается просматривать массив Words на предмет совпадений с текущим словом, и, если совпадения не обнаружится, добавим слово в массив в позицию WordCount, а саму границу WordCount отодвинем дальше. Если же найдено совпадение, то нужно просто увеличить на единицу значение частоты для данного элемента в параллельном массиве WordFrq.

Вторая переменная, FoundFlag, должна играть роль флажка: “Есть совпадение!”, поэтому она должна принадлежать к логическому типу Boolean (переменные этого типа могут принимать только значения True или False — Истина или Ложь).

см. также в приложении раздел “Boolean”.

Итак, в переменной MyWord содержится очередное слово документа. Перед просмотром массива сбросим флажок FoundFlag — совпадений пока не найдено:

FoundFlag = False

Затем “сканируем” массив Words при помощи переменной цикла I вплоть до значения, содержащегося в счетчике WordCount:

For I = 0 To WordCount

If UCase(Trim(MyWord)) = Words(I) Then

WordFrq(I) = WordFrq(I) + 1

FoundFlag = True

End If

Next I

Если текущее слово документа совпало с одним из слов, зарегистрированных в массиве Words, то соответствующий элемент в массиве WordFrq (то есть частота данного слова) будет

увеличен на единицу. Одновременно установим флажок FoundFlag в положение True, сигнализируя о том, что слово встретилось в массиве и регистрировать его не нужно.

А вот если слово не найдено (FoundFlag = False, или, что то же самое, Not FoundFlag), то необходимо добавить его в массив Words, записав 1 в соответствующей позиции массива частот и передвинув на 1 вперед счетчик WordCount:

If Not FoundFlag Then

Words(WordCount) = UCase(Trim(MyWord)) WordFrq(I) = WordFrq(I) + 1

WordCount = WordCount + 1

...

End If

Поскольку в этой ветви условной конструкции увеличивается значение переменной WordCount, необходимо здесь же проверить, не достигло ли оно границы массива — если, да, то надо что-то предпринять, поскольку запись в несуществующий элемент массива приведет к ошибке выполнения:

Частотный словарь 115

If WordCount >= MaxWords - 1 Then

MsgBox "Все слова не поместились!"

GoTo Finish

End If

Здесь, при помощи оператора перехода GoTo осуществляется “аварийный” выход из цикла For Each MyWord…Next MyWord, поэтому метку Finish поместим за его пределами.

см. также в приложении раздел “Переход по метке”.

После окончания работы цикла массив Words будем содержать все отличающиеся слова документа с указанием их частот в соответствующих позициях массива WordFrq.

Создание Word-документов и новых абзацев в них при помощи метода Add

Теперь осталось создать новый документ Word и сформировать в нем частотный словарь. Чтобы каждая позиция словаря располагалась на отдельной строке документа, необходимо для каждой позиции создавать новый абзац. Создание абзаца подобно созданию документа. В обоих случаях следует использовать заранее объявленные объектные переменные соответствующих типов,…

Dim MyDocument As Document

Dim MyParagraph As Paragraph

а затем присвоить им значения при помощи оператора Set и метода Add соответствующего семейства (семейства документов или абзацев):

Set MyDocument = Documents.Add

Set MyParagraph = MyDocument.Paragraphs.Add

Чтобы вставить перед символом вновь созданного абзаца какой-либо текст (в данном случае

— заголовок словаря), следует воспользоваться методом InsertBefore:

MyParagraph.Range.InsertBefore "Слово,Частота"

Теперь остается создать заданное число (то есть, WordsInDictionary) строк словаря. Это было бы простой задачей, если бы массив Words был упорядочен по частотам.

Упорядочивание элементов массива по убыванию

Придется объединить создание строк с упорядочиванием массива. Для этого необходимо просто найти в массиве слово с самой большой частотой, вывести его в результирующий документ и вслед за этим исключить из рассмотрения, присвоив ему частоту, равную нулю. После этого можно снова искать слово с наибольшей частотой — это будет второе по частоте слово словаря. И так далее — аналогичные действия должны быть выполнены WordsInDictionary раз. Понятно, что основой конструкции должен быть цикл вида:

For M = 1 To WordsInDictionary

...

Next M

Чтобы найти слово с наибольшей частотой необходимо “просканировать” массив WordFrq и найти самое большое число, сравнивая каждый очередной элемент массива с некоторой переменной. Пусть это будет целочисленная переменная N, которой вначале необходимо присвоить значение 0:

N = 0

116 Глава 4. Практикум программирования на VBA для Excel и Word

For G = 0 To WordCount

If WordFrq(G) > N Then

N = WordFrq(G)

WordPointer = G

End If

Next G

В результате номер слова с самой большой частотой будет сохранен в переменной

WordPointer.

Теперь следует сформировать очередную строку словаря:

Set MyParagraph = MyDocument.Paragraphs.Add

MyParagraph.Range.InsertBefore _

(Words(WordPointer) + "," + Str(WordFrq(WordPointer)))

Как и заголовок, каждая строка будет состоять из слова и числа, характеризующего его частоту, разделенные запятой. Наконец, в конце очередного прохода цикла следует обнулить частоту только что обработанного слова и задача оказывается сведенной в точности к предыдущему шагу: требуется найти слово с самой большой частотой …

WordFrq(WordPointer) = 0

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

Преобразование строк Word-документа в таблицу при помощи метода ConvertToTable

Подобно команде Таблица | Преобразовать | Преобразовать в таблицу, метод

ConvertToTable применим к любому текстовому диапазону и требует того же набора параметров. Параметр NumColumns задает число столбцов. Константа, присвоенная в качестве значения параметру Format, определяет один из вариантов автоформата таблицы. Наконец, параметр

Separator назначает символ-разделитель (вот для чего нужна была запятая между словами в строках словаря!). Итак, получаем следующее выражение:

MyDocument.Range.ConvertToTable _

NumColumns:=2, Format:=wdTableFormatList4 _

, Separator:=","

ПРИМЕЧАНИЕ

Справедливости ради нужно отметить, что такой подход несколько отличается от того, который предлагается компанией Microsoft: по правилам должны использоваться специальные константы вида wdSeparator. Но данная книга предполагает освоение практического освоения программирования на Microsoft VBA и часто предлагает нестандартные, но эффективные подходы для решения офисных задач. По мнению автора предложенный метод в данном примере проще и понятней начинающему пользователю, а главное проверен на практике и работает.

см. подробнее фирменную документацию по Microsoft VBA или раздел справочной системы Microsoft Office, посвященный VBA.

Автоформат таблицы

Метод ConvertToTable будет применен ко всему текстовому диапазону документа

(MyDocument.Range) сразу, в результате чего из всех имеющихся строк словаря будет создана таблица с применением автоформата “Список4” (именно этому значению соответствует константа

Частотный словарь 117

wdTableFormatList4 — для того чтобы узнать имена констант, соответствующих другим форматам таблицы, необходимо воспользоваться справкой VBA.

В конечном итоге исходный текст макроса должен выглядеть следующим образом (листинг

4.3).

ЛИСТИНГ 4.3

Public Sub Частотный_словарь()

Const MaxWords = 3000

Const WordsInDictionary = 200

Dim Words(0 To MaxWords) As String

Dim WordFrq(0 To MaxWords) As Integer

Dim MyWord As Object

Dim WordCount, WordPointer As Integer

Dim FoundFlag As Boolean

Dim MyParagraph As Paragraph

Dim MyDocument As Document

Dim MyTable As Table

Dim I, N, M, G As Integer

WordCount = 0

For I = 0 To MaxWords

Words(I) = ""

WordFrq(I) = 0

Next I

For Each MyWord In ActiveWindow.Document.Words

If Len(Trim(MyWord)) > 2 Then

FoundFlag = False

For I = 0 To WordCount

If UCase(Trim(MyWord)) = Words(I) Then

WordFrq(I) = WordFrq(I) + 1

FoundFlag = True

End If

Next I

If Not FoundFlag Then

Words(WordCount) = UCase(Trim(MyWord))

WordFrq(I) = WordFrq(I) + 1

WordCount = WordCount + 1

If WordCount >= MaxWords - 1 Then

MsgBox "Все слова не поместились!"

GoTo Finish

End If

118 Глава 4. Практикум программирования на VBA для Excel и Word

End If

End If

Next MyWord

Finish:

Set MyDocument = Documents.Add

Set MyParagraph = MyDocument.Paragraphs.Add

MyParagraph.Range.InsertBefore "Слово,Частота"

For M = 1 To WordsInDictionary

N = 0

For G = 0 To WordCount

If WordFrq(G) > N Then

N = WordFrq(G)

WordPointer = G

End If

Next G

Set MyParagraph = MyDocument.Paragraphs.Add

MyParagraph.Range.InsertBefore _

(Words(WordPointer) + "," + Str(WordFrq(WordPointer)))

WordFrq(WordPointer) = 0

Next M

MyDocument.Range.ConvertToTable _

NumColumns:=2, Format:=wdTableFormatList4 _

, Separator:=","

MyDocument.Activate

End Sub

Сравнительный анализ прайс-листов 119

Рис. 4.3. В окне Word сформирован новый документ, содержащий частотный словарь с заданными параметрами

Использование процедуры Частотный_словарь

Чтобы проверить макрос в действии, откроем произвольный документ.

Затем, выбрав в строке меню Word команду Сервис | Макрос | Макросы, следует выделить в списке Èìÿ пункт Частотный_словарь и щелкнуть на кнопке Выполнить. В результате будет сформирован новый документ с частотным словарем (рис. 4.3) того документа, который был активным на момент запуска макроса.

Сравнительный анализ прайс-листов

В предыдущих главах уже рассматривался прайс-лист ООО “Универсал”. Однако прайс-лист фирмы — не единственная из разновидностей прайс-листов, с которыми приходится иметь дело в офисе. Часто приходится иметь дело с прайс-листами других фирм, выступая при этом в роли клиента — потенциального покупателя и, как любой покупатель, обладать правом выбора. Очень часто право выбора превращается в необходимость выбрать наилучший по ценовым или другим показателям вариант — от эффективности закупок зависит, в конечном счете, успешное функционирование фирмы.

Как и прайс-лист ООО “Универсал”, прайс-листы других фирм обычно выполнены в форме Excel-таблицы. Все они отличаются не только по содержанию, но и по оформлению. В разных офисах по-разному используют возможности Excel и, как следствие, среди реально существующих и обращающихся прайс-листов наблюдается поистине великое разнообразие. Если речь идет об одиночных товарных позициях, то автоматизация средствами VBA мало, чем поможет в анализе прайс-листов потенциальных или действующих поставщиков.

Рис. 4.4. Прайс-лист “А”

Но для однородных товаров в широком ассортименте очень часто возникает нужда в сравнительном анализе двух или более прайс-листов, принадлежащих разным поставщикам. В разделе прайса, состоящем из нескольких десятков или, тем более, сотен позиций, не так-то просто найти нужное наименование. Если же речь идет о нахождении соответствующей позиции в других прайс-листах и сравнении цен, то выполнить такую операцию вручную можно только для одного-

120 Глава 4. Практикум программирования на VBA для Excel и Word

нескольких товаров. Для десятков и сотен товарных позиций выполнить сравнительный анализ по нескольким прайс-листам возможно только при помощи средств автоматизации. Попробуем решить такую задачу “силами” VBA.

Входящие прайс-листы

Предположим, что в работе используются прайс-листы двух фирм-поставщиков. По сравнению с внутренним прайс-листом, который является исходящим, прайс-листы других фирм будут входящими. Как нетрудно будет убедиться, решение задачи для двух прайс-листов без труда можно распространить на любое число прайсов.

Пусть это будут прайс-листы по уже привычной для нас теме — расходным материалам. И пусть интерес вызывает раздел, касающийся картриджей для принтеров. Только на этот раз надо взглянуть на данный раздел с точки зрения потенциального покупателя.

Возьмем в качестве материала для упражнений два прайс-листа, принадлежащих реальным фирмам, которые занимаются продажей офисных расходных материалов и комплектующих. Первый прайс лист (назовем его “А”), содержит раздел “Картриджи к струйным принтерам EPSON”, при этом в столбце “Part Number” отображается техническое наименование картриджа, а в столбце “Розница” — розничная цена в пересчете на доллары США (см. рис. 4.4).

Другой прайс лист (назовем его “Б”), устроен несколько иным образом. То же самое техническое наименование отображается здесь в столбце “Артикул”, а розничной цене в пересчете на доллары США соответствует четвертый ценовой столбец (первые три столбца предназначены для покупателей, обладающих некоторой “историей покупок”). Фрагмент этого прайс-листа изображен на рис. 4.5.

Рис. 4.5. Прайс-лист ”Б”

Постановка задачи

Как сопоставить эти прайс-листы? Или, поставим задачу шире — как обеспечить их автоматическое сопоставление ведь фирмы “А” и “Б” регулярно обновляют свои прайсы и было бы очень неплохо, если бы сотрудник ООО “Универсал” мог отслеживать, как абсолютные, так и относительные изменения в прайс листах. Кроме изменений разницы в цене по некоторой позиции между прайс-листами разных фирм (относительные изменения) менеджера по закупкам могут интересовать изменения в ценах одной и той же фирмы, то есть разница в ценах между двумя версиями одного и того же прайса (абсолютные изменения). Иными словами, требуется создать универсаль-

Сравнительный анализ прайс-листов 121

ный механизм сравнения произвольных прайс-листов, который можно было бы применять, сообразуясь с текущими потребностями офиса.

Если бы все фирмы поставляли один и тот же набор картриджей, то проблема решалась бы просто: достаточно было бы отсортировать два прайса по наименованию картриджа в алфавитном порядке и поместить рядом соответствующие столбцы. При этом позиции всех товаров в обоих прайсах совпали бы. Однако на практике, конечно, все обстоит сложнее. Даже если абсолютное большинство позиций присутствуют в обоих прайс-листах одновременно, нескольких отличающихся позиций будет достаточно, чтобы списки не совпали ни при какой сортировке. И тут уж не обойтись без помощи VBA….

Сводный прайс-лист

Что может сделать макрос VBA в такой ситуации? Он может создать сводный список по двум или более прайс-листам, где присутствовали бы все позиции, встречающиеся в анализируемых прайсах. При этом каждая позиция будет встречаться только один раз, и соответствующие этой позиции цены, для всех прайс-листов, где такая позиция имеется, будут расположены в одной строке. После этого сопоставление цен по анализируемым прайс-листам станет совершенно тривиальной задачей, которую можно решить, как при помощи VBA, так и обычными средствами рабочего листа Excel. Можно, например, задать условное форматирование для соответствующих столбцов, которое меняло бы вид ячеек в зависимости от соотношения цен. Другой вариант — добавление столбца с “разностными” формулами, где соотношение будет видно по знаку разницы. Наконец, такой список нетрудно проанализировать, просто взглянув на него на экране.

Подключение анализируемых прайс-листов

Чтобы создать универсальный механизм сопоставления прайс-листов, нужно абстрагироваться от конкретных деталей, которые могут отличаться в реальных случаях. Здесь возможны различные подходы. Например, можно связать посредством технологии Automation некоторые столбцы в анализируемых прайс-листах со сводным прайсом, с тем, чтобы любые изменения в исходных таблицах автоматически отображались в рабочей книге, служащей носителем сводного прайслиста. Другой возможный подход заключается в том, чтобы ориентироваться на абстрактные именованные диапазоны — если макрос будет иметь дело с диапазонами, скажем, “Наименование” и “Цена”, то внутреннее устройство любого конкретного прайс-листа не будет иметь значения. Достаточно будет указать в каждом прайс-листе диапазоны с такими именами, и для выполнения макроса больше ничего не потребуется.

Начнем с того, что укажем такие диапазоны в прайс-листах, выбранных для упражнения. В прайс листе “А” диапазону “Наименование” соответствуют ячейки, начиная с ячейки C20 и ниже, вплоть до конца раздела. Диапазон “Цена” задается выделением ячеек, начиная с ячейки F20 и ниже, до конца раздела. Например, для задания именованного диапазона “Цена” необходимо, выделив ячейки, щелкнуть мышью в поле имени и ввести туда строку “Цена”, как это показано на рис. 4.6.

Аналогичные дейчтвия необходимо проделать с прайс-листом “Б“. Это позволит абстрагироваться от деталей конкретного устройства различных прайс-листов и в роли прайсов “А“ и “Б“ смогут выступить любые прайс-листы, где указаны соответствующие именованные диапазоны.

см. также в гл. 2 раздел “Именованные диапазоны”, где описан еще один метод создания именованного диапазона.

122 Глава 4. Практикум программирования на VBA для Excel и Word

Рис. 4.6. На рабочем листе прайса заданы именованные диапазоны ”Наименование“ и “Цена“

Подготовка рабочего листа для сводного прайса

Далее нам следует подготовить рабочий лист, на котором должен отображаться сводный прайс. Лист должен содержать столбец “Наименование“, отформатированный для ввода текстовых значений, и два (или более, по числу анализируемых прайсов) столбца с ценами, которые необходимо отформатировать для ввода значений в соответствующем денежном формате. Примерный вид такого рабочего листа изображен на рис. 4.7.

Рис. 4.7. Рабочий лист для отображения сводного прайс-листа

Для создания и запуска макроса выберем простейший способ, предпочтение которому следует отдать в силу его универсальности: макрос с именем Сводка следует создать при помощи команды Сервис | Макрос | Макросы (необходимо ввести в поле Имя макроса строку “Сводка” и щелкнуть на кнопке Создать), а запустить его можно будет опять же при помощи команды Сервис | Макрос | Макросы (для этого необходимо выделить в списке имен строку “Сводка” и щелкнуть на кнопке Выполнить).

ПРИМЕЧАНИЕ

Конечно, в зависимости от конкретных обстоятельств, вопрос запуска макроса можно решить и по-другому. Если рабочий лист, на котором предполагается создать сводный прайс, является одним из нескольких листов рабочей книги, то код макроса можно поместить в обработчик события рабочего листа Activate, при этом макрос будет выполняться в момент, когда лист станет текущим. Если этот лист единственный в книге, то событие Activate для него при открытии книги

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