Срезы в 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]
Здесь интересно то, что в качестве одного из индексов можно указывать не срез, а число - тогда возвращается массив пониженной размерности. Так, в трех последних примерах результатами будут одномерные массивы: первая строка матрицы, последняя строка матрицы и последний столбец матрицы.
Необходимо обратить внимание, что отсутствует возможность создавать безопасные многомерные срезы и использовать многомерные срезы в левой части оператора присваивания.