PascalABC.NET
October 23, 2022

Срезы в PascalABC.NET

Срезы массивов - великолепное средство, позволяющее просто решать огромное количество задач.

Срезы имеют синтаксис, очень близкий к срезам в языке Python.

Срезы в PascalABC.NET можно использовать для массивов, списков List<T> и строк. Всё дальнейшее изложение мы будем проводить, используя срезы массивов.

Простейшие срезы

В простейшем случае срез массива - это подмассив некоторого массива с элементами, расположенными подряд.

Например, если у нас есть массив

var a := Arr(0..9);

заполненный числами

0 1 2 3 4 5 6 7 8 9

то запись

a[k1:k2]

означает подмассив a с индексами от k1 до k2-1. Таким образом, k2 - номер элемента, следующего за последним элементом среза. Например,

a[2:6]

описывает срез с элементами

2 3 4 5

Кроме того, индексы в срезе можно опускать. Срез

a[:3] 

описывает элементы с начала массива до индекса 2:

0 1 2

а срез

a[5:]

описывает элементы от пятого до последнего:

5 6 7 8 9

Наконец, запись

a[:]

используется для создания копии всего массива

Срезы с элементами, индексируемыми с конца

В PascalABC.NET элементы массива, индексируемые с конца, как и в C#, обозначаются:

a[^1] - первый с конца элемент (последний)
a[^2] - второй с конца элемент (предпоследний)

В комбинации со срезами получаются интересные примеры:

a[1:^1] - срез без первого и последнего элемента
a[^2:] - срез из двух последних элементов

Срезы с шагом

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

var a := Arr(0..9);

все элементы с четными индексами задаются срезом

a[::2]

а все элементы с нечетными индексами - срезом

a[1::2]

Срезы с отрицательным шагом

Шаг в срезе может быть отрицательным. Например, для массива

var a := Arr(0..9);

срез

a[k1:k2:-1]

подразумевает, что k2<=k1 и в срез включаются элементы от k1 до k2+1 справа налево. Например, срез

a[5:2:-1]

захватывает элементы

5 4 3

Обратим внимание, что когда мы хотим срезать элементы до начала, то в качестве второго значения среза надо указывать -1:

a[5:-1:-1]

захватывает элементы

5 4 3 2 1 0

Наконец, срез

a[::-1]

означает реверсированный массив a.

Использование срезов для циклического сдвига элементов

Задача циклического сдвига элементов компактно решаются с помощью срезов.

Циклический сдвиг влево на k элементов:

var k := 3;
var a := Arr(0..9);
a.Println;
a := a[k:] + a[:k];
a.Println;

Вывод:

0 1 2 3 4 5 6 7 8 9
3 4 5 6 7 8 9 0 1 2

Циклический сдвиг вправо на k реализуется циклическим сдвигом влево на a.Length - k:

var k := 3;
var a := Arr(0..9);
a.Println;
k := a.Length - k;
a := a[k:] + a[:k];
a.Println;

Вывод:

0 1 2 3 4 5 6 7 8 9
7 8 9 0 1 2 3 4 5 6

Безопасные срезы

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

var a := Arr(0..9);

следующие срезы дадут исключение:

a[10:]
a[2:11]
a[-1:]

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

Безопасные срезы записываются со знаком вопроса:

a?[k1:k2:step]

Например, если мы предыдущие срезы превратим в безопасные:

a?[10:].Println;
a?[2:11].Println;
a?[-1:].Println;

то получим следующий вывод:

2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9

Обратим внимание, что в первом случае безопасный срез даёт массив с нулевым количеством элементов.

Методы, применяемые к срезам

Ряд классических задач можно легко решать, используя срезы и срезы с шагом.

Задача 1. Найти сумму элементов, стоящих на четных местах

Решение.

a[::2].Sum.Print;

Задача 2. Найти минимальный среди элементов первой половины массива

Решение.

a[:5].Min.Print;

Задача 3. В массиве с четным количеством элементов получить массив пар рядом стоящих элементов

Решение.

var b := a[::2].ZipTuple(a[1::2]).ToArray;
b.Println;

Результат

(0,1) (2,3) (4,5) (6,7) (8,9)

Срезы в левой части оператора присваивания

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

Основное - срез в левой части должен совпадать по длине с массивом в правой части.

По-прежнему, пусть дан массив a = Arr(0..9)

Пример 1. Присвоить элементам на нечетных местах значение 777

Решение

a[1::2] := |777| * 5;
a.Println;

Результат:

0 777 2 777 4 777 6 777 8 777

Многомерные срезы

В PascalABC.NET начиная с версии 3.8 можно использовать многомерные срезы. Рассмотрим их на примере двумерного массива

var m := Matr(||1,2,3,4|,|5,6,7,8|,|9,10,11,12||);
Println(m);

Вывод:

[[1,2,3,4],[5,6,7,8],[9,10,11,12]] 

Тогда следующие многомерные срезы дадут:

Println(m[:,:]);           // [[1,2,3,4],[5,6,7,8],[9,10,11,12]] - копия
Println(m[1:3,1:4]);       // [[6,7,8],[10,11,12]]  
Println(m[::2,::3]);       // [[1,4],[9,12]]
Println(m[::-2,::-1]);     // [[12,11,10,9],[4,3,2,1]] 
Println(m[^2::-1,^2::-1]); // [[7,6,5],[3,2,1]] 
Println(m[:^1,:^1]);       // [[1,2,3],[5,6,7]] 
Println(m[1,:]);           // [5,6,7,8] 
Println(m[^1,:]);          // [9,10,11,12] 
Println(m[:,^1]);          // [4,8,12] 

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

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