Редактировать

В 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.

Повторять этот текст в каждой задаче - странно. Для этого есть стандартные функции.

Циклы по массиву

Для обработки элементов массива используются следующие циклы:

  1. Цикл for по индексам (если требуется менять элементв или нужна информация об индексах)
    for var i:=0 to a.Length-1 do
      a[i] *= 2;
    
  2. Цикл foreach по элементам (если индексы не видны и мы не меняем массив)
    var sum := 0;
    foreach var x in a do
      sum += x;
    
  3. Цикл foreach по индексам
    foreach var i in a.Indices do
      a[i] += 2;
    
  4. Цикл 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.

Методика. Поскольку данная запись использована здесь впервые, обращаем внимание на её высокую универсальность: алгоритмы фильтрации и поиска суммы не слиты в один алгоритм, а используются порознь один за другим, что позволяет:

  1. Лучше читать код (потому что он записан компактно и методами с понятными и очевидными названиями)
  2. Лучше модифицировать код
  3. Решать более сложные и более прикладные задачи за одно и то же время урока

Далее лямбда-выражения объясняются подробно и тщательно и используются повсеместно.

Операции с массивами

x in a - возвращает true если значение x содержится в a
a1 + a2 - возвращает массив, образованный слиянием массивов a1 и a2
a1 * n - возвращает массив, состоящий из n раз повторенных значений массива a

Изменение размера динамического массива

Если в процессе работы программы требуется чтобы динамический массив менял свой размер, то следует … пользоваться типом List! Это - динамический массив с возможностью эффективного измненения размера и рядом дополнительных методов. Основным является методы Add - добавить в конец:

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 для контейнера при решении задач определяется тем, будет ли данный контейнер расширяться по ходу работы программы.