18c0693f

Задача 10 Нахождение медианы


Постановка задачи: Для массива M и элемента Cand вычислить разность между двумя числами - числом элементов массива M, больших и меньших Cand.

Это вариация задачи о медиане - "среднем" элементе - массива. Медиану можно определить, например, таким алгоритмом: упорядочив массив, взять элемент, находящийся в середине. Есть и более эффективные алгоритмы. Но я решил ограничиться более простой задачей - проверкой на "медианность". Заметим: если все элементы массива M различны и число их нечетно, то, взяв медиану в качестве Cand, искомая в задаче 10 разность даст значение 0. В общем случае, близость искомой разности к нулю является мерой близости параметра Cand к медиане массива M. Но займемся программистскими аспектами этой задачи. У функции, ее реализующей, на входе - массив, а на выходе - скаляр. Мы хотели бы, чтобы эта функция могла вызываться в формулах рабочего листа, а в качестве фактического параметра ей могли быть переданы как объект Range, так и массив чисел. Вот как я реализовал эту функцию, назвав ее IsMediana:

Public Function IsMediana(M As Variant, Cand As Variant) As Integer 'Дан массив M и элемент Cand. В качестве результата возвращается 'разность между числом элементов массива M, больших и меньших Cand. 'Тем самым можно определить близость Cand к медиане массива M. Dim Pos As Integer, Neg As Integer Pos = 0: Neg = 0 'Анализ типа параметра M If TypeName(M) = "Range" Then For i = 1 To M.Rows.Count For j = 1 To M.Columns.Count If M.Cells(i, j) > Cand Then Pos = Pos + 1 ElseIf M.Cells(i, j) < Cand Then Neg = Neg + 1 End If Next j Next i IsMediana = Pos - Neg

ElseIf TypeName(M) = "Variant()" Then 'TypeName is "Variant()" 'Это массив, но не совсем настоящий, для него не определены, 'например, функции границ: LBound, UBound. Dim Val As Variant For Each Val In M If Val > Cand Then Pos = Pos + 1 ElseIf Val < Cand Then Neg = Neg + 1 End If Next Val IsMediana = Pos - Neg Else MsgBox ("При вызове функции: IsMediana(M,Cand)" & _ vbCrLf & "M не является массивом или объектом Range!") End If


End Function
Прокомментируем работу функции IsMediana.

Посмотрим, как это выглядит на экране, и разберем примеры нескольких различных вызовов функции IsMediana в формулах рабочего листа:

увеличить изображение
Рис. 2.5.  Вызов функции IsMediana в формулах рабочего листа
На рабочем листе сформированы два массива: вектор M, вытянутый в виде столбца, и прямоугольная матрица N. Вектор M записан в ячейках C6:C11, матрица N - в F5:I6. В ячейки E8:E16 я поместил формулы, вызывающие функцию IsMediana. Они не являются формулами над массивами, несмотря на то, что параметром может быть массив рабочего листа. Важно, что результат - скаляр. Если бы результат, возвращаемый функцией, был массивом, формулу следовало бы вызывать как формулу над массивами. Для скалярного результата это не так.
Хочу обратить внимание на то, что Excel не последователен в этом вопросе. Пользовательская функция, написанная на VBA, аргументы которой являются массивами, но результат которой есть скаляр, может вызываться, как я сказал чуть выше, в обычных формулах рабочего листа. Что касается вызова встроенных функций, то тут ситуация сложнее. Если функции, возвращающей скаляр, передается один массив, то она может вызываться как обычная функция. Если же ей передается несколько массивов, то такой вызов уже должен быть формулой над массивами. Вот классический пример двух вызовов:
=СУММ(A1:D1) {=СУММ(A1:D1 +A2:D2)}


Первый вызов работает нормально, а второй должен быть обязательно формулой над массивами, чтобы результатом действительно была сумма всех элементов двух объектов Range.
Но вернемся к рассмотрению нашей функции IsMediana. В двух первых вызовах функции IsMediana, записанных в ячейках E8, E9, ей передается в качестве параметров имя массива рабочего листа "M" и разные кандидаты: 7 и 8. Они оба годятся на роль медианы этого массива. В следующих двух вызовах проверяются кандидаты на медиану массива N. Как видите, 4 ближе к медиане, чем 3. Следующие два вызова в ячейках E12 и E13 демонстрируют возможность задания диапазона ячеек в момент вызова, что позволяет, например, работать с частью массива. В двух последующих вызовах вообще не используются в качестве входных данных элементы рабочего листа. Входным параметром M является массив констант, заключенный в фигурные скобки, элементы которого разделяются символом ";". В этих случаях фактический параметр уже не является объектом Range, а имеет тип массива с элементами Variant. Поэтому и функция IsMediana будет работать по-другому, в отличие от предыдущих вызовов. Разбор случаев при данном вызове, зависящий от результата, возвращаемого функцией TypeName, приведет к выбору второго варианта. Наконец, вызов, записанный в формуле ячейки E16, демонстрирует случай, когда входной параметр M - обычное число и, следовательно, не является ни объектом Range, ни массивом. Как следствие, разбор случаев в функции IsMediana приводит к третьему варианту и появлению на экране окна сообщений.



Все публикации раздела