Информатика
.pdf
51
For i = 1 To n
For j = 1 To n
If i = j Then A(i, j) = 2 Else A(i , j) = 1 / (i - j)
Next
Next : s = 0
For i = 1 To n - 1
For j = i + 1 To n
For k = 1 To n
s = s + A(k, i) *A(k, j)
Next
Next
Next: Scalar = s
End Function .
В Mcd все можно записать значительно проще:
Scalar(n) : = |
|
for |
i 0 .. n - 1 |
|
|
|
|
|
|||
|
|
for j 0 .. n – 1 |
|
|
|
|
|
|
|
1 |
|
|
|
|
Ai, j ← if i = j, 2, |
i - j |
|
|
|
n - 2 |
n - 1 |
|
|
|
|
∑ |
∑ (A‹i›∙A‹j›) |
|
|
|
|
i = 0 |
j = i + 1 |
|
|
|
|
|
|
||
Здесь использован символ суммирования, который выбирается в меню анализа (см. рис. 8), вызываемом щелчком мышью по иконке
.
Как уже отмечалось (и это заметно по приводимым примерам), что в Mcd особенно удобно ра-
ботать с векторами и матрицами. Пусть, например, Рис. 8 необходимо вычислять экспоненту матрицы с по-
мощью ряда exp(A) = eA = ∑ n∞= 0 n!1 An. В VBA пришлось бы создавать
подпрограммы умножения матриц (см. пример 2), сложения матриц, оценки максимального по модулю элемента (нормы матрицы), деления матрицы на число. Mcd справляется с этим совсем просто:
Exp(A) : = ε ← 0.00001, n ← 0
q ← identity(cols(A)), S ← q
(→)
while max |q| > ε
n ← n + 1 q ← n-1∙q∙A
S ← S + q
S
Помимо уже использованных ранее встроенных функций здесь участвует функция identity(), возвращающая единичную матрицу указанно-
→
го (в качестве аргумента) размера, и операция векторизации (f(M), см.
52
рис. 6, - функция f применяется к каждому элементу массива М). Функция max() возвращает наибольший элемент из списка аргумен-
|
|
|
|
2 |
-1 |
0 |
|
|
|
|
|
-1 |
3 |
-2 |
|
функция вычислит зна- |
|
тов. В частности, для матрицы A= |
|
|||||||
|
|
|
|
0 |
-2 |
1 |
|
|
15.096 |
-23.829 |
12.071 |
|
|
|
|
|
|
чение Exp(A) = -23.829 |
63.066 |
-35.588 |
. |
|
|
|
|
|
12.829 |
-35.588 |
21.443 |
|
|
|
|
|
|
Упражнение. Создайте соответствующую программу в VBA.
Пример 6. Рассмотрим еще пример, где использование массивов играет вспомогательную роль. Пусть для заданного набора n натуральных чисел a1, a2, … , an требуется вычислить сумму
a 1 |
a 2 |
a n |
1 |
|
|
|
S = ∑ |
∑ |
… ∑ |
|
. |
||
1+ j1 + j2 +… + jn |
||||||
j 1 = 0 j 2 = 0 |
j n = 0 |
|
||||
Каждая отдельная сумма, естественно, вычисляется с помощью цикла For … Next, но количество таких циклов заранее неизвестно. Можно создать вектор счетчиков и с его помощью выполнить суммирование. Приведем пример программы (функция Ubound() возвращает верхнюю границу одномерного массива) в VBA.
Function Summarize(a() As Integer) As Single |
|
Dim n As Integer, p() As Integer, sum As Single |
|
Dim r As integer, i As Integer, j As Integer |
|
n = Ubound(a): Redim p(n): sum = 1 |
'***подготовка информации |
Do: r = 0 |
|
For i = n To 0 Step -1 |
|
If p(i) < a(i) Then |
|
p(i) = p(i) + 1 |
'***заполнение массива |
For j = 0 To n: If j > i Then p(j) = 0 |
|
r = r + p(j) |
'***суммирование знаменателя |
Next |
|
sum = sum + 1 / (r + 1): Exit For |
'***прерывание цикла |
End If |
|
Next |
|
Loop Until r = 0: Summarize = sum
End Function .
В Mcd, как уже отмечалось выше, средства циклических вычислений весьма упрощены. Предусловие (по своему смыслу) требует предварительной подготовки условия для запуска цикла, конструкция Do ...
Loop отсутствует полностью, прерывание цикла (вместо Exit For) выполняет оператор break. В результате программа в Mcd выглядит несколько иначе (напомним еще раз, что на рабочем листе Mcd разрешается записывать лишь один оператор в строке, здесь это правило нарушается в целях компактности и экономии места):
53
:
Summarize(a) : = n ← last(a), pn ← 0, sum ← 1
r ← 1 while r > 0
r ← 0
for i n .. 0
if pi < ai
pi ← pi + 1
for j 0 .. n
pj ← 0 if j > i r ← r + pj
sum ← sum + 1 r + 1
break
sum
1.7.1.Программные стэки
Впрограмме можно организовать неизменяемый стэк данных, используемых локально в данном месте. В DOS-версии Бейсика для этой цели использовалась инструкция DATA, за которой следовал произвольный набор данных. Данные можно было прочитать с помощью оператора READ по порядку слева направо (счетчик стэка можно восстановить с помощью инструкции RESTORE). В VBA эти служебные слова несут другую нагрузку, поэтому такая удобная конструкция упразднена. Использование типа Variant позволяет организовать аналогичную службу с более широкими возможностями. В переменную типа Variant можно поместить информацию любого основного типа с помощью оператора присваивания и "функции" Array, параметрами которой являются элементы информации, разделенные запятыми. Ссылка на них осуществляется так же, как на элементы массива, индексированного с нуля (можно изменить начало индексации инструкцией Option Base 1). Функция Ubound() возвращает верхний индекс этого массива. При использовании его в программе можно не знать объем содержимого, при этом допускается следующий вид цикла пересчета:
For Each ‹переменная› In ‹массив› ... Next,
в этом случае будут выбираться все элементы массива по порядку. Пример. Пусть, например, квадратный массив n×n заполнен
случайными целыми числами. Элемент массива будет называться "горкой", если его значение больше всех значений его ближайших соседей. Нужно посчитать количество горок. Будем индексировать все массивы с нуля. Заметим, что индексы ближайших соседей различаются лишь на единицу. Для элемента Ai, j его ближайшими соседями
будут Ai, j+1, Ai+1, j+1, Ai+1, j, Ai+1, j-1, Ai, j-1, Ai-1,j-1, Ai-1, j, Ai-1, j+1. Поэтому
54
можно завести массив изменения индексов Ind = Array("Инкремент", 0, 1, 1, 1, 1, 0, 1, -1, 0, -1, -1, -1, -1, 0, -1, 1) и с его помощью сравнивать содержимое соседних ячеек. Приведем код VBA.
Function Hills(n As Integer) As Integer
Dim A() As Integer, i As Integer, j As Integer, k As Integer, b As Boolean Dim ii As Integer, jj As Integer, s As Integer, Ind As Variant
Ind = Array("increment",0,1,1,1,1,0,1,-1,0,-1,-1,-1,-1,0,-1,1)
Redim A(n - 1, n - 1): Randomize Timer '***подготовка генерации сл. чисел For i = 0 To n - 1
For j = 0 To n - 1 |
|
|
A(i, j) = Int(100*Rnd – 50) |
'***создан массив случайных чисел |
|
Next |
|
|
Next |
|
|
For i = 0 To n - 1 |
|
|
For j = 0 To n - 1 |
|
|
b = True |
|
|
For s = 1 To 8 |
|
|
ii = Ind(2*s - 1): jj = Ind(2*s) |
'***выбор индексов |
|
If i+ii >=0 And i+ii < n And j+jj >=0 And j+jj < n Then |
||
If A(i, j) <= A(i+ii, j+jj) Then |
'***проверка на горку |
|
b = False: Exit For |
|
'***это – не горка |
End If |
|
|
End If |
|
|
Next: If b Then k = k + 1 |
|
'***а это – горка |
Next |
|
|
Next: Hills = k
End Function .
Как уже указывалось, в Mcd массивы также имеют (по умолчанию) тип Variant, но отсутствует логический тип, так что код выглядит несколько иначе:
|
|
|
|
|
|
Hills(n) : = |
Ind ← ("Increment" 0 1 1 1 1 0 1 -1 0 -1 -1 -1 -1 0 -1 1)T |
||||
|
for |
i 0 .. n – 1 |
|||
|
for j 0 .. n – 1 |
||||
|
|
|
Ai, j ← trunc(rnd(100) – 50) |
||
|
k ← 0 |
||||
|
for |
i 0 .. n – 1 |
|||
|
for j 0 .. n – 1 |
||||
|
|
|
b ← 1 |
||
|
|
|
|||
|
|
|
for s 1 .. 8 |
||
|
|
|
|
ii ← Ind2∙s – 1, jj ← Ind2∙s |
|
|
|
|
|
||
|
|
|
|
if Ai, j ≤ Ai + ii, j + jj if i+ii ≥ 0 i+ii < n j+jj ≥ 0 j+jj < n |
|
|
|
|
|
|
b ← 0, break |
|
|
|
|
|
|
|
k |
|
k ← k + 1 if b > 0 |
||
|
|
||||
|
|
|
|
|
|
Замечательно, что в отличие от стэков DATA содержимое массивов Array можно произвольно менять в программе.
55
1.7.2. Тип пользователя
Пользователь имеет право определить свой тип, обладающий свойствами некоторых стандартных типов или типов, определенных пользователем ранее. Свойства типа (они называются "полями") указываются внутри "скобок" Type ‹имя типа› ... End Type, а сам тип должен быть определен в отдельном модуле для того, чтобы компилятор создал соответствующую конструкцию. При обращении к переменной пользовательского типа название конкретного свойства (или поля)
отделяется от имени типа переменной точкой. Рассмотрим
Пример. Пусть имеется 10 фамилий некоторых лиц, о которых следует хранить такую информацию: фамилия, возраст, пол, номер телефона. В отдельном модуле определим тип Person следующим образом:
Type Person |
|
Name As String*10 |
'***фамилия (не более 10 символов) |
Aged As Byte |
'***возраст |
Sex As Boolean |
'***пол (муж.): истина - ложь |
Telef As Long |
'***длинное целое число |
End Type . |
|
Теперь можно создать массив в 10 персон стандартным образом, например, Dim Staff(9) As Person (этот тип уже определен). Организуем ввод необходимой информации в цикле:
For i = 0 To 9
Staff(i). Name = InputBox("Введите фамилию", "","") Staff(i). Aged = Val(InputBox("Введите возраст", "", ""))
Staff(i). Sex = InputBox("Введите пол: True (муж.), False (жен.)", "", "") Staff(i). Telef = Val(InputBox("Введите номер телефона", "", ""))
Next .
Аналогичным образом можно и выводить информацию. Для сокращения записи можно использовать конструкцию With ‹имя› ... End With, внутри которой указываются лишь обрабатываемые поля, предваренные точкой. Таким образом, предыдущий код может быть записан несколько короче:
For i = 0 To 9 With Staff(i)
.Name = InputBox("Введите фамилию", "","")
.Aged = Val(InputBox("Введите возраст", "", ""))
.Sex = InputBox("Введите пол: True (муж.), False (жен.)", "", "")
.Telef = Val(InputBox("Введите номер телефона", "", "")) End With
Next .
Предположим теперь, что из всех данных нужно выбрать лишь мужчин старше 50 лет:
56
For i = 0 To Ubound(Staff)
With Staff(i)
If .Sex And .Aged > 50 Then _
MsgBox .Name & ", Aged " & Format(.Aged)
End With
Next .
Теперь предположим, что одно из свойств нашего типа тоже пользовательское. Пусть это тип Student с полями: факультет, курс, наличие стипендии (да, нет). Определим типы:
Type Student
Department As String
Course As Byte
Scholarship As Boolean
End Type
Type Person
Name As String*10
Aged As Byte
Sex As Boolean
Business As Student
Telef As Long
End Type .
и переменную Dim Casanova As Person. Теперь поле Business само имеет тип пользователя и свои собственные поля. Для того, чтобы отметить факт получения стипендии, следует выполнить присваивание
Casanova.Business.Scholarship = True,
а чтобы заполнить все поля, можно использовать вложенные конст-
рукции With ... End With:
With Casanova
.Name = ...
.Aged = ...
.Sex = ...
With .Business
.Department = ...
.Course = ...
.Scholarship = ...
End With
End With .
Отметим, что цикл For Each ... In ... Next с пользовательской переменной не работает, он используется лишь с типом Variant.
57
1.7.3.Сортировка массивов
Впрактике обработки массивов часто требуется (для облегчения поиска, например), чтобы информация содержалась в массиве в определенном порядке (числовая, например, в порядке возрастания значений, текстовая – в алфавитном порядке, и т.д.). Сортировка – довольно распространенная операция над массивами, поэтому в Excel, например, она включена в стандартное меню, а Mcd имеет функции sort() – сортировка одномерного массива (вектора) в порядке возрастания, csort() – сортировка матрицы по заданному столбцу, rsort() – сортировка матрицы по данной строке.
Существует довольно много алгоритмов сортировки, поясним лишь два самых простых, применимых к массивам произвольной размерности. Один – с выбором главного элемента, второй – метод "пузырька".
А. Выбор главного элемента. Будем упорядочивать массив по возрастанию элементов: просматривается весь (одномерный) ряд значений, выбирается наименьшее из них и ставится на первое место, затем среди оставшихся выбирается наименьшее и ставится на второе место, и т.д. Для одномерного числового массива А(n) в VBA алгоритм имеет вид:
For i = 0 To n: p = A(i): k = i |
|
For j = i + 1 To n: q = A(j) |
|
If q < p Then |
'***выбор минимального элемента |
p = q: k = j |
|
End If |
|
Next |
|
q = A(i): A(i) = A(k): A(k) = q |
'***перестановка элементов |
Next . |
|
Если бы требовалось упорядочить массив по убыванию, то следовало бы знак неравенства в третьей строке заменить на противоположный. Если массив двумерный, например, А(m, n) и его следует отсортировать по столбцу с номером s, то пришлось бы ввести дополнительный цикл:
For i = 0 To m: p = A(i, s): k = i |
|
For j = i + 1 To m: q = A(j, s) |
|
If q < p Then |
'***выбор минимального элемента |
p = q: k = j |
|
End If |
|
Next |
|
For j = 0 To n |
|
q = A(i, j): A(i, j) = A(k, j): A(k, j) = q '***перестановка элементов Next
Next .
Б. Метод "пузырька". В предыдущей ситуации в одномерном массиве просматриваются все соседние пары. Если в какой-нибудь па-
58
ре нарушается условие порядка, то элементы этой пары меняются местами. После такого просмотра самый большой элемент переместится в конец массива (бегущий "пузырек"). Далее просматривается массив с начала до предпоследнего элемента и выполняется та же процедура проверки. Затем до предпредпоследнего, и т.д. Приведем соответствующий код для одномерного массива A(n).
For i = n To 1 Step -1
For j = 0 To i – 1
If A(j) > A(j + 1) Then
q = A(j): A(j) = A(j + 1): A(j + 1) = A(j)
End If
Next
Next .
Аналогично предыдущему, если необходимо упорядочить двумерный массив А(m, n) по столбцу с номером s, то следует поступить в соответствии с кодом:
For i = m To 1 Step -1
For j = 0 To i – 1
If A(j, s) > A(j + 1, s) Then For k = 0 To n
q = A(j, k): A(j, k) = A(j + 1, k): A(j + 1, k) = A(j, k) Next
End If
Next Next .
По трудоемкости эти алгоритмы одинаковы, так что выбор определяется лишь личным предпочтением. В Mcd, как уже отмечено, сортировка входит в список встроенных функций и там нет необходимости в выборе алгоритма.
Сложнее дело обстоит в случае создания упорядоченного массива. Это бывает полезно, когда поступает произвольная информация, но хранить ее нужно в упорядоченном виде для дальнейшего использования. Как и выше, будем полагать, что нас интересует порядок по возрастанию значений (или алфавитный порядок). Тут возникает вопрос вставки нового значения в существующий массив без нарушения порядка.
Рассмотрим популярный алгоритм двоичного выбора: выбранный диапазон индексов массива делится пополам и выясняется, в какую половину следует вводить данные, выбранная половина снова делится пополам, и т.д. до тех пор, пока окончательный участок не будет содержать единственный элемент, сравнение с которым очевидно. Например, в список (-2, 0, 1.5, 7, 7.2, 11) нужно ввести число 8.6. Очевидно, (так как 8.6 > 1.5) нужно выбрать вторую половину списка (7, 7.2, 11). В ней в середине стоит число 7.2 < 8.6, значит, число
59
помещается между 7.2 и 11. Осталось передвинуть последнее число в следующую позицию (возможно, увеличив размер массива), а в освободившееся место поместить 8.6. Таким образом, процесс вставки в массив состоит из двух операций: первая – поиск места вставки, вторая – освобождение места для новой информации.
Запишем первую операцию – поиск места, т.е. индекса элемента массива, после которого нужно вставлять новый. Идея алгоритма допускает рекурсивный поиск (см. п. 1.4). Запишем его в виде функции.
Function Index(a() As ‹тип›, b As ‹тип›, p As Integer, q As Integer) As Integer Dim res As Integer, k As Integer
If b < a(p) Then res = p – 1
ElseIf b > a(q) Then res = q
ElseIf q – p = 1 Then res = p
Else
k = (p + q) \ 2 '***целочисленное деление
If b < a(k) Then res = Index(a(), b, p, k) Else res = Index(a(), b, k, q) End If: Index = res
End Function .
Вторая операция – вставка – реализуется проще: если возвращаемый предыдущей функцией индекс отрицательный (при индексации с нуля), то все элементы массива нужно сдвинуть и в первую позицию вставить новую информацию, иначе это нужно сделать с указанного индекса (предполагается, что размер массива это позволяет):
Sub Insert(ByRef a() As ‹тип›, b As ‹тип›, n As Integer) Dim i As Integer, m As Integer
m = Index(a(), b, 0, n) If m < 0 Then
For i = n To 0 Step -1 a(i + 1) = a(i)
Next: a(0) = b
ElseIf m = n Then a(n + 1) = b
Else
For i = n To m + 1 Step -1 a(i + 1) = a(i)
Next: a(m + 1) = b End If
End Sub .
Текстовые массивы (списки) сортируются точно также, поскольку знаки "<", ">" сравнивают коды символов (лексикографически). Если сортируются массивы пользовательского типа, то в сравнении указывается то поле, по которому выполняется сортировка. Например, если необходимо организовать массив данных типа Person (пример п. 1.7.2) в алфавитном порядке (по фамилиям), то в функции
60
Index(a() As Person, b As Person, p As Integer, q As Integer)
следует записывать сравнения в виде
If b.Name < a(p).Name Then
...
В Mcd, в целом, функции выглядят проще. Кроме того, встроенная функция stack(▪, ▪, ▪) (объединение массивов по вертикали) позволяет автоматически увеличивать массив данных до необходимых размеров.
Index(a, b, p, q) : = res ← p - 1 if b < ap
otherwise
res ← q if b > aq otherwise
res ← p if q - p = 1 otherwise
k← trunc p+q
2
res ← if(b < ak, Index(a, b, p, k), Index(a, b, k, p)
res
Insert(a, b) : = a ← stack(a, (0)), n ← last(a), m ← Index(a, b, 0, n - 1) if m < 0
for i n - 1 .. 0
ai + 1 ← ai
a0 ← b
an ← b if m = n – 1 otherwise
for i n - 1 .. m + 1
ai + 1 ← ai am + 1 ← b
a
На первый взгляд, кажется проще простого набрать данные, как попало, а потом отсортировать нужным образом (по крайней мере, сортировка готового массива выглядит проще). Но если (например, в базах данных, где размеры массивов превышают сотни и тысячи) информация постоянно пополняется и регулярно используется, то вопрос о выборе алгоритма решается не столь однозначно.
Упражнение 1. Попробуйте создать массив из десяти случайных чисел, равномерно распределенных на заданном отрезке и упорядоченных по возрастанию.
Рассмотрим пример, в котором выбор порядка играет определяющую роль. Это решение систем линейных уравнений методом Гаусса. Суть метода в том, что выбираются последовательно диагональные
