В PascalABC.NET рекомендуется использовать динамические массивы. В отличие от статических, они имеют огромное количество методов и операций, просты в создании, заполнении и выводе.
Описание и выделение памяти
Динамический массив описывается так:
begin
var a: array of integer;
end.
Память под динамический массив a выделяется в момент работы программы:
begin
var a: array of integer;
var n := ReadInteger;
a := new integer[n];
end.
Здесь - первое преимущество динамических массивов - в переменной a может храниться массив любого размера, память выделяется в процессе работы программы. Кроме того, выделенная память гарантированно автоматически заполняется нулевыми значениями.
Можно совместить описание и выделение памяти - тип динамического массива выводится автоматически:
begin
var n := ReadInteger;
var a := new integer[n];
end.
Обычно в PascalABC.NET совмещают описание динамического массива, выделение памяти и заполнение значениями. Самый простой способ - заполнить n нулями:
begin
var n := ReadInteger;
var a := |0| * n;
end.
Индексация в динамических массивах и использование статических массивов
Динамические массивы индексируются с нуля - это эффективно. В качестве индексов в динамических массивах могут выступать только целые.
Статические массивы тем не менее иногда удобно использовать - в задачах, где индексы либо символьные, либо по-существу начинаются не с нуля. Например, для подсчёта количества слов на каждую букву может использоваться стаический массив
var a := array ['a'..'z'] of integer;
Заполнение статических массивов - увы - производится в цикле. Кроме того, они не помнят свою длину и передача таких массивов в качестве параметров подпрограмм связана с техническими сложностями 40-летней давности, не нужными начинающим.
Простейшее заполнение
Важную роль играют функции заполнения динамических массивов. Перед заполнением они выделяют для массива память, поэтому в одной строке можно совмещать описание, выделение памяти и заполнение.
Простейшее заполнение - набором значений:
var a := |1,3,3,7,9|;
Заполнение диапазоном целых или символьных значений делается с использованием функции Arr:
var a := Arr(1..9);
var b := Arr('a'..'z');
Заполнение определённым значением осуществляется с помощью операции умножения массива на число:
begin
var n := ReadInteger;
var a := |0| * n; // массив из n нулей
end.
Для заполнения можно также использовать функцию ArrFill:
begin
var n := ReadInteger;
var a := ArrFill(n,0); // массив из n нулей
end.
Для заполнения массива случайными значениями следует использовать
begin
var n := ReadInteger;
var a := ArrRandomInteger(n); // по умолчанию значения от 0 до 100
var a1 := ArrRandomInteger(n,1,10); // случайные от 1 до 10
var r := ArrRandomReal(n); // по умолчанию значения от 0 до 10
var r1 := ArrRandomReal(n,2,5); // случайные вещественные от 2 до 5
end.
Не рекомендуется использовать алгоритм для заполнения массива случайными в каждой задаче:
begin
var n := ReadInteger;
var a := new integer[n];
for var i:=0 to n-1 do
a[i] := Random(0,100);
end.
Повторять этот текст в каждой задаче - странно. Для этого есть стандартные функции.
Ввод и вывод элементов массива
Для ввода элементов массива базовых типов используются функции
begin
var n := ReadInteger;
var a := ReadArrInteger(n);
var r := ReadArrReal(n);
var s := ReadArrString(n);
// ...
end.
Стандартная процедура вывода Write или Print выводит значения в массиве в квадратных скобках черезх запятую:
begin
var a := Arr(1..9);
Print(a); // [1,2,3,4,5,6,7,8,9]
end.
Однако лучше всего для вывода воспользоваться методом Print, выводящим все значения в массиве через пробел:
begin
var a := Arr(1..9);
a.Print; // 1 2 3 4 5 6 7 8 9
end.
Не рекомендуется вводить и выводить элементы массива в цикле
begin
var n := ReadInteger;
var a := new integer[n];
for var i:=0 to n-1 do
a[i] := ReadInteger;
end.
Повторять этот текст в каждой задаче - странно. Для этого есть стандартные функции.
Циклы по массиву
Для обработки элементов массива используются следующие циклы:
- Цикл for по индексам (если требуется менять элементв или нужна информация об индексах)
for var i:=0 to a.Length-1 do a[i] *= 2;
- Цикл foreach по элементам (если индексы не видны и мы не меняем массив)
var sum := 0; foreach var x in a do sum += x;
- Цикл foreach по индексам
foreach var i in a.Indices do a[i] += 2;
- Цикл foreach по диапазону индексов
var (K,L) := ReadInteger2; foreach var i in K..L do a[i] := 777;
Пример. Найти количество чётных элементов, стоящих на чётных местах
begin
var a := ArrRandomInteger(10);
a.Println;
var count := 0;
foreach var i in a.Indices do
if i.IsEven and a[i].IsEven then
count += 1;
Print(count);
end.
Методы массива
Массивы содержат большое количество стандартных методов:
a.Length - длина массива
a.Min - минимальный элемент в массиве
a.Max - максимальный элемент в массиве
a.IndexMin - индекс первого минимального элемента в массиве
a.IndexMax - индекс первого максимального элемента в массиве
a.Sum - сумма элементов в числовом массиве
a.Product - произведение элементов в числовом массиве
a.Average - среднее элементов в числовом массиве
a.First - первый элемент в массиве
a.Last - последний элемент в массиве
a.IndexOf(x) - индекс первого значения x или -1 если не найдено
a.Replace(x,y) - заменить в массиве все значения x на y
Кроме того, доступны процедуры
Sort(a) - сортировка элементов по возрастанию
SortDescending(a) - сортировка элементов по убыванию
Reverse(a) - инвертирование элементов массива
Методика. Обращаем внимание, что в методических целях естественно рассказывать, как эти алгоритмы устроены “внутри”. Но потом следует пользоваться стандартными алгоритмами, а не заставлять учеников во всех задачах использовать рукописные сортировки или рукописный поиск минимума. Например, рекомендуется показать, как накопить сумму элементов массива:
begin
var a := ArrRandomInteger(10);
a.Println;
var sum := 0;
foreach var x in a do
sum += x;
Print(sum);
end.
Здесь следует обратить внимание, что этот алгоритм может быть легко модифицирован в алгоритм нахождения суммы элементов по условию: например, всех чётных элементов:
begin
var a := ArrRandomInteger(10);
a.Println;
var sum := 0;
foreach var x in a do
if x.IsEven then
sum += x;
Print(sum);
end.
Отметим, что заполнение случайными и вывод - это технические части программы, которые делаются в PascalABC.NET в одну строку, позволяя концентрироваться на алгоритме.
Если условие надо накладывать на индексы, то в этом случае (и только в этом случае) следует использовать цикл for по индексам:
begin
var a := ArrRandomInteger(10);
a.Println;
var sum := 0;
for var i:=0 to a.Length-1 do
if i.IsEven then
sum += a[i];
Print(sum);
end.
Для нахождения суммы без условия необходимо использовать стандартный метод a.Sum:
begin
var a := ArrRandomInteger(10);
a.Println;
Print(a.Sum);
end.
Отметим также, что для поиска суммы по условию также имеется короткая однострочная запись. Она требует использование стандартного метода Where с параметром, являющимся лямбда-выражением. Лямбда-выражения мы будем рассматривать далее:
begin
var a := ArrRandomInteger(10);
a.Println;
Print(a.Where(x -> x.IsEven).Sum);
end.
Методика. Поскольку данная запись использована здесь впервые, обращаем внимание на её высокую универсальность: алгоритмы фильтрации и поиска суммы не слиты в один алгоритм, а используются порознь один за другим, что позволяет:
- Лучше читать код (потому что он записан компактно и методами с понятными и очевидными названиями)
- Лучше модифицировать код
- Решать более сложные и более прикладные задачи за одно и то же время урока
Далее лямбда-выражения объясняются подробно и тщательно и используются повсеместно.
Операции с массивами
x in a - возвращает true если значение x содержится в a
a1 + a2 - возвращает массив, образованный слиянием массивов a1 и a2
a1 * n - возвращает массив, состоящий из n раз повторенных значений массива a
Изменение размера динамического массива
Если в процессе работы программы требуется чтобы динамический массив менял свой размер, то следует … пользоваться типом List
begin
var l := new List<integer>;
l.Add(1);
l.Add(3);
l.Add(5);
l.Print
end.
Для первоначального заполнения списков List используется короткая фунеция Lst:
begin
var l := Lst(1,3,5);
l.Print
end.
При необходимости список List можно преобразовать к динамическому массиву, вызвав метод .ToArray:
begin
var l := Lst(1,3,5);
var a := l.ToArray;
end.
Большинство методов, которые имеются в массивах, есть и в списках List. Поэтому выбор типа List или array of для контейнера при решении задач определяется тем, будет ли данный контейнер расширяться по ходу работы программы.