Что такое диапазон массива
Перейти к содержимому

Что такое диапазон массива

Диапазоны (Ranges) в C# 8

​Пусть в нашей программе есть массив целых чисел numbers :

Перед нами стоит задача: получить новый массив, вырезав из массива numbers элементы от индекса 2 до индекса 4 включительно, то есть должен получится массив [4, 2, 3] .

Решение 1

Самое первое и простое решение, которое приходит в голову — это решение в лоб:

Создадим результирующий массив целых чисел result размером 3 :

Пройдемся циклом по нужным индексам массива numbers , а именно с 2 до 4 включительно:

Запишем в результирующий массив result нужные значения:

Выведем массив result и убедимся, что все ОК:

С задачей мы справились. Но есть некоторые недостатки:

Для решения такой маленькой задачи, пришлось пройтись циклом.

По коду не сразу понятно, что он делает. Таким образом страдает читаемость.

Также можно ошибиться с индексами (относится к начинающим программистам).

Следовательно, такое решение нас не устраивает.

Решение 2

Немногие знают, что у списка ( List ) есть готовый метод GetRange(int index, int count) , который получает из списка нужный диапазон элементов. Метод первым параметром принимает index — индекс начала диапазона, а вторым параметром count — количество элементов, которые нужно получить. Например:

GetRange(0, 5) — получает 5 элементов, начиная с индекса 0.

GetRange(3, 10) — получает 10 элементов, начиная с индекса 3.

Тогда сделаем следующее:

Для того чтобы мы воспользовались готовым методом GetRange , преобразуем массив в список с помощью метода ToList :

Воспользуемся методом GetRange . Нам нужно взять 3 элемента, начиная с индекса 2 :

Метод GetRange вернул результат в виде списка ( List<int> ). Для того чтобы преобразовать его в массив, воспользуемся методом ToArray :

Выведем массив result и убедимся, что все ОК:

С задачей мы справились. Но есть некоторые недостатки:

Для решения такой маленькой задачи, пришлось воспользоваться тремя дополнительными методами.

По коду не сразу понятно, что он делает. Таким образом страдает читаемость.

Также можно ошибиться при передаче параметров в метод GetRange (относится к начинающим программистам).

Данные преобразования ресурсоемкие по памяти и производительности. Вызовы ToList , ToArray проходятся по коллекции и выделяют новую память.

Следовательно, такое решение нас не устраивает.

Решение 3

Можно еще воспользоваться технологией LINQ , а именно двумя методами:

Skip(int count) — возвращает все элементы коллекции, кроме первых count .

Take(int count) — возвращает первые count элементов коллекции.

В нашем случае, для того, чтобы взять элементы массива от индекса 2 до индекса 4 включительно, нужно пропустить 2 элемента последовательности, а затем взять первые 3 элемента. Как раз получатся элементы с индексами от 2 до 4 .

Посмотрим в коде:

С задачей мы справились. Но есть некоторые недостатки:

Для решения такой маленькой задачи, пришлось воспользоваться тремя дополнительными методами.

Можно ошибиться при передаче параметров в методы Skip и Take (относится к начинающим программистам).

Данные преобразования ресурсоемкие по памяти и производительности.

Следовательно, такое решение нас не устраивает.

Решение 4

Есть еще статический метод Copy у класса Array :

Данный метод копирует элементы из одного массива в другой. Давайте поясним каждый параметр:

Array sourceArray — массив, с которого копируем элементы.

int sourceIndex — с какого индекса из массива sourceArray начинаем копировать элементы.

Array destinationArray — массив, в который копируются элементы.

int destinationIndex — начиная с какого индекса в результирующем массиве destinationArray вставляются элементы.

int length — количество элементов, которое нужно скопировать.

Давайте воспользуемся данным методом:

Создадим результирующий массив целых чисел result размером 3 :

Вызываем метод Copy . Передаем массив numbers и индекс 2 — откуда начинаем вырезать элементы. Затем передаем результирующий массив result и индекс 0 — с какого индекса вставляются элементы. А затем передаем 3 — количество элементов, которое нужно скопировать:

Выведем массив result и убедимся, что все ОК:

С задачей мы справились. Но есть некоторые недостатки:

Легко можно ошибиться при передаче параметров в метод Copy (относится к начинающим программистам).

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

Следовательно, такое решение нас не устраивает.

Решение 5

В C# 8 версии добавили дополнительную функциональность для работы с диапазонами ( Range ). Теперь для решения нашей задачи можно написать вот так:

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

numbers[3..10] — вырезает элементы, начиная с индекса 3 и заканчивая индексом 9 . Напоминаю, что правая граница не включается.

numbers[1..7] — вырезает элементы, начиная с индекса 1 и заканчивая индексом 6 .

Если индексы будут равны между собой, то в результате получится массив нулевой длины:

Если первый индекс будет больше второго индекса, то возникнет исключение ArgumentOutOfRangeException во время выполнения программы:

Можно использовать также индексацию справа налево ( Indices ), введенную тоже в C# 8 версии, про которую говорили совсем недавно:

Можно делать еще более веселые штучки:

Например, для получения первых n элементов с помощью диапазонов, нужно написать numbers[0..n] . Так вот, специально для случаев, когда вы хотите взять диапазон с начала массива (когда первый индекс равен 0 ), придумали упрощение: можно индекс равный 0 опускать, то есть написать вот так: numbers[..n] . Такая запись более предпочтительна.

Например, для получения всех элементов, кроме первых n с помощью диапазонов, нужно написать numbers[n..numbers.Length] . Специально для случаев, когда вы хотите взять все элементы, кроме первых n (начиная с индекса n и до конца массива), придумали упрощение. Так как второй индекс всегда равен длине массива, то его можно опустить, то есть написать вот так: numbers[n..] . Такая запись более предпочтительна.

Ну и комбинация этих двух подходов. Для получения полной копии массива, можно написать вот так: numbers[..] , то есть опустить оба индекса. Это означает взять диапазон от начала массива до конца.

Что там под капотом?

На самом деле любой диапазон в C# 8 версии можно хранить в новом типе данных Range . Он находится в пространстве имен ( namespace ) System , следовательно, никакой дополнительный using при его использовании не нужно писать.

У Range существует два конструктора:

Range() – создает пустой диапазон.

Range(Index start, Index end) – создает диапазон от индекса start (включительно) и до индекса end (НЕ включительно).

Рассмотрим на примерах:

Заметьте, что объект типа Range передается в качестве индекса в квадратные скобки ( [] ).

Проведем соответствие между двумя разными записями:

Укороченная версия

Версия с Range

numbers[new Range(2, 5)]

numbers[new Range(^6, ^2)]

В Range реализовано неявное преобразование укороченной записи (например 2..5 ) к Range . Вот как это работает:

У Range переопределен метод Equals :

А можно вообще вот так:

Здесь сначала происходит неявное преобразование укороченной записи к Range , а потом вызов Equals .

У Range переопределен также метод ToString :

Заметьте, что для индексации с конца выводится ^ перед индексом.

Также теперь мы можем в методы передавать диапазон:

Выводы:

Структура Range позволяет создать экземпляр, к которому можно обращаться многократно.

Индексы и диапазоны

Диапазоны и индексы обеспечивают лаконичный синтаксис для доступа к отдельным элементам или диапазонам в последовательности.

В этом руководстве вы узнаете, как:

  • Использовать этот синтаксис для диапазонов в последовательности.
  • Проектировать начало и конец каждой последовательности.
  • Составлять сценарии для типов Index и Range.

Поддержка языков для индексов и диапазонов

Поддержка языков опирается на два новых типа и два новых оператора:

    представляет индекс в последовательности.
  • Оператор ^ (индекс с конца), который указывает, что индекс указан относительно конца последовательности. представляет вложенный диапазон последовательности.
  • Оператор диапазона .. , который задает начало и конец диапазона в качестве своих операндов.

Начнем с правил для использования в индексах. Рассмотрим массив sequence . Индекс 0 совпадает с sequence[0] . Индекс ^0 совпадает с sequence[sequence.Length] . Выражение sequence[^0] создает исключение так же, как и sequence[sequence.Length] . Для любого числа n индекс ^n совпадает с sequence.Length — n .

Последнее слово можно получить с помощью индекса ^1 . Добавьте следующий код после инициализации:

Диапазон указывает начало и конец диапазона. Диапазоны являются открытыми, то есть конечное значение не включается в диапазон. Диапазон [0..^0] представляет весь диапазон так же, как [0..sequence.Length] представляет весь диапазон.

Следующий код создает поддиапазон со словами «quick», «brown» и «fox». Он включает в себя элементы от words[1] до words[3] . Элемент words[4] в диапазон не входит.

Следующий код возвращает диапазон со словами «lazy» и «dog». Он включает элементы words[^2] и words[^1] . Конечный индекс words[^0] не включен. Добавьте также следующий код:

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

Диапазоны или индексы можно также объявлять как переменные. Впоследствии такую переменную можно использовать внутри символов [ и ] :

Следующий пример демонстрирует многие возможные причины для таких решений. Измените x , y и z и опробуйте различные комбинации. Поэкспериментируйте со значениями, у которых x меньше y , а y меньше, чем z для допустимых сочетаний. Добавьте в новый метод приведенный ниже код. Опробуйте различные комбинации:

Поддержка типов для индексов и диапазонов

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

Любой тип, который предоставляет индексатор с параметром Index или Range, явно поддерживает индексы или диапазоны соответственно. Индексатор, который принимает один параметр Range, может возвращать другой тип последовательности, например System.Span<T>.

Производительность кода, использующего оператор range, зависит от типа операнда последовательности.

Временная сложность оператора range зависит от типа последовательности. Например, если последовательность является string или массивом, то результатом будет копия указанного раздела входных данных, поэтому временная сложность имеет значение O(N) (где N — это длина диапазона). С другой стороны, если это System.Span<T> или System.Memory<T>, результат ссылается на то же резервное хранилище, что означает отсутствие копии и использование операции O(1) .

Помимо временной сложности, создаются дополнительные выделения и копии, влияющие на производительность. В коде, чувствительном к производительности, рекомендуется использовать Span<T> или Memory<T> в качестве типа последовательности, так как оператор range не предоставляет им выделений.

Тип является счетным, если у него есть свойство с именем Length или Count с доступным методом получения и типом возвращаемого значения int . Счетный тип, который не поддерживает индексы или диапазоны явным образом, может предоставить неявную поддержку для них. Дополнительные сведения см. в разделах о поддержке неявного индекса и неявного диапазона в примечании к предлагаемой функции. Диапазоны с поддержкой неявных диапазонов возвращают тот же тип последовательности, что и исходная.

Например, следующие типы .NET поддерживают как индексы, так и диапазоны: String, Span<T> и ReadOnlySpan<T>. List<T> поддерживает индексы, но не поддерживает диапазоны.

Array реализует более сложное поведение. Одномерные массивы поддерживают как индексы, так и диапазоны. Многомерные массивы не поддерживают индексаторы или диапазоны. Индексатор для многомерного массива имеет не один, а несколько параметров. Массивы массивов также поддерживают и диапазоны и индексаторы. В следующем примере показано выполнение итерации для прямоугольного подраздела массива массивов. В этом примере выполняется итерация раздела в центре с исключением трех первых и последних строк, а также двух первых и последних столбцов из каждой выбранной строки:

Во всех случаях оператор range для Array выделяет массив для хранения возвращаемых элементов.

Сценарии для индексов и диапазонов

Часто диапазоны и индексы используются для анализа отдельной части последовательности большего размера. В новом синтаксисе легче понять, о какой части последовательности идет речь. Локальная функция MovingAverage принимает Range в качестве аргумента. После этого метод перечисляет только это диапазон и вычисляет минимальное, максимальное и среднее значение. Попробуйте добавить в свой проект следующий код:

Примечание по индексам и массивам диапазона

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

Что такое диапазон массива

В C# 8.0 была добавлена новая функциональность — индексы и диапазоны, которые упрощают получение из массивов подмассивов. Для этого в C# есть два типа: System.Range и System.Index . Оба типа являются структурами. Тип Range представляет некоторый диапазон значений в некоторой последовательность, а тип Index — индекс в последовательности.

Индексы

Индекс фактически представляет числовое значение, и при определении индекса мы можем указать это значение:

В данном случае индекс представляет третий элемент последовательности (индексация начинается с 0).

С помощью специального оператора ^ можно задать индекс относительно конца последовательности.

Теперь индекс представляет второй элемент с конца последовательности, то есть предпоследний элемент.

Используем индексы для получения элементов массива:

Фактически для данной задачи индексы не нужны, и мы можем воспользоваться стандартными возможностями массивов:

То есть в подобных ситуациях плюсом индексов является большая удобочитаемость. Так, people[^2] более читабельно, чем people[people.Length — 2] .

Диапазон

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

В данном случае диапазон myRange1 влючает элементы с 1 индекса по 4-й индекс (не включая). При этом элемент по 4-му индексу не включается в диапазон. При этом границы диапазона задаются не просто числами, а именно объектами Index. То есть следующие определения диапазонов будут равноценны:

Практическое применение диапазонов — получим со второго по четвертый элементы массива:

Результатом операции people[1..4] является подмассив элементов с 1 по 3 индексы (включая). Консольный вывод:

Мы можем задать для диапазона только конечный индекс. В этом случае начальным индексом по умолчанию будет 0.

Либо, наоборот, задать только начальный индекс, тогда конечным индексом будет последний индекс последовательности:

Используя индексы относительно конца последовательности, можно получать диапазон относительно конца последовательности:

Кроме массивов индексы и диапазоны также применяются к объектам Span и ReadOnlySpan:

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *