- PVSM.RU - https://www.pvsm.ru -
Пусть в нашей программе есть массив целых чисел numbers:
static void Main()
{
var numbers = new int[] { 5, 1, 4, 2, 3, 7 };
}
Перед нами стоит задача: получить новый массив, вырезав из массива numbers элементы от индекса 2 до индекса 4 включительно, то есть должен получится массив [4, 2, 3].
Самое первое и простое решение, которое приходит в голову — это решение в лоб:
Создадим результирующий массив целых чисел result размером 3:
static void Main()
{
var numbers = new int[] { 5, 1, 4, 2, 3, 7 };
var result = new int[3];
}
Пройдемся циклом по нужным индексам массива numbers, а именно с 2 до 4 включительно:
static void Main()
{
var numbers = new int[] { 5, 1, 4, 2, 3, 7 };
var result = new int[3];
for (int i = 2; i <= 4; i++)
{
}
}
Запишем в результирующий массив result нужные значения:
static void Main()
{
var numbers = new int[] { 5, 1, 4, 2, 3, 7 };
var result = new int[3];
for (int i = 2; i <= 4; i++)
{
result[i - 2] = numbers[i];
}
}
Выведем массив result и убедимся, что все ОК:
static void Main()
{
var numbers = new int[] { 5, 1, 4, 2, 3, 7 };
var result = new int[3];
for (int i = 2; i <= 4; i++)
{
result[i - 2] = numbers[i];
}
Console.WriteLine(string.Join(" ", result)); // 4 2 3
}
С задачей мы справились. Но есть некоторые недостатки:
Для решения такой маленькой задачи, пришлось пройтись циклом.
По коду не сразу понятно, что он делает. Таким образом страдает читаемость.
Также можно ошибиться с индексами (относится к начинающим программистам).
Следовательно, такое решение нас не устраивает.
Немногие знают, что у списка (List) есть готовый метод GetRange(int index, int count), который получает из списка нужный диапазон элементов. Метод первым параметром принимаем index — индекс начала диапазона, а вторым параметром count — количество элементов, которые нужно получить. Например:
GetRange(0, 5) — получает 5 элементов, начиная с индекса 0.
GetRange(3, 10) — получает 10 элементов, начиная с индекса 3.
Тогда сделаем следующее:
Для того чтобы мы воспользовались готовым методом GetRange, преобразуем массив в список с помощью метода ToList:
static void Main()
{
var numbers = new int[] { 5, 1, 4, 2, 3, 7 };
var list = numbers.ToList();
}
Воспользуемся методом GetRange. Нам нужно взять 3 элемента, начиная с индекса 2:
static void Main()
{
var numbers = new int[] { 5, 1, 4, 2, 3, 7 };
var list = numbers.ToList();
var resultList = list.GetRange(2, 3);
}
Метод GetRange вернул результат в виде списка (List<int>). Для того чтобы преобразовать его в массив, воспользуемся методом ToArray:
static void Main()
{
var numbers = new int[] { 5, 1, 4, 2, 3, 7 };
var list = numbers.ToList();
var resultList = list.GetRange(2, 3);
var result = resultList.ToArray();
}
Выведем массив result и убедимся, что все ОК:
static void Main()
{
var numbers = new int[] { 5, 1, 4, 2, 3, 7 };
var list = numbers.ToList();
var resultList = list.GetRange(2, 3);
var result = resultList.ToArray();
Console.WriteLine(string.Join(" ", result)); // 4 2 3
}
С задачей мы справились. Но есть некоторые недостатки:
Для решения такой маленькой задачи, пришлось воспользоваться тремя дополнительными методами.
По коду не сразу понятно, что он делает. Таким образом страдает читаемость.
Также можно ошибиться при передаче параметров в метод GetRange (относится к начинающим программистам).
Данные преобразования ресурсоемкие по памяти и производительности. Вызовы ToList, ToArray проходятся по коллекции и выделяют новую память.
Следовательно, такое решение нас не устраивает.
Можно еще воспользоваться технологией LINQ, а именно двумя методами:
Skip(int count) — возвращает все элементы коллекции, кроме первых count.
Take(int count) — возвращает первые count элементов коллекции.
В нашем случае, для того, чтобы взять элементы массива от индекса 2 до индекса 4 включительно, нужно пропустить 2 элемента последовательности, а затем взять первые 3 элемента. Как раз получатся элементы с индексами от 2 до 4.
Посмотрим в коде:
static void Main()
{
var numbers = new int[] { 5, 1, 4, 2, 3, 7 };
var temp = numbers.Skip(2).Take(3);
var result = temp.ToArray();
Console.WriteLine(string.Join(" ", result)); // 4 2 3
}
С задачей мы справились. Но есть некоторые недостатки:
Для решения такой маленькой задачи, пришлось воспользоваться тремя дополнительными методами.
Можно ошибиться при передаче параметров в методы Skip и Take (относится к начинающим программистам).
Данные преобразования ресурсоемкие по памяти и производительности.
Следовательно, такое решение нас не устраивает.
Есть еще статический метод Copy у класса Array:
Copy(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length)
Данный метод копирует элементы из одного массива в другой. Давайте поясним каждый параметр:
Array sourceArray — массив, с которого копируем элементы.
int sourceIndex — с какого индекса из массива sourceArray начинаем копировать элементы.
Array destinationArray — массив, в который копируются элементы.
int destinationIndex — начиная с какого индекса в результирующем массиве destinationArray вставляются элементы.
int length — количество элементов, которое нужно скопировать.
Давайте воспользуемся данным методом:
Создадим результирующий массив целых чисел result размером 3:
static void Main()
{
var numbers = new int[] { 5, 1, 4, 2, 3, 7 };
var result = new int[3];
}
Вызываем метод Copy. Передаем массив numbers и индекс 2 — откуда начинаем вырезать элементы. Затем передаем результирующий массив result и индекс 0 — с какого индекса вставляются элементы. А затем передаем 3 — количество элементов, которое нужно скопировать:
static void Main()
{
var numbers = new int[] { 5, 1, 4, 2, 3, 7 };
var result = new int[3];
Array.Copy(numbers, 2, result, 0, 3);
}
Выведем массив result и убедимся, что все ОК:
static void Main()
{
var numbers = new int[] { 5, 1, 4, 2, 3, 7 };
var result = new int[3];
Array.Copy(numbers, 2, result, 0, 3);
Console.WriteLine(string.Join(" ", result)); // 4 2 3
}
С задачей мы справились. Но есть некоторые недостатки:
Легко можно ошибиться при передаче параметров в метод Copy (относится к начинающим программистам).
Не сразу понятно как использовать метод Copy, ведь он ничего не возвращает. Нужно понять, что результат возвращается в массиве, который был передан третьим параметром.
Следовательно, такое решение нас не устраивает.
В C# 8 версии добавили дополнительную функциональность для работы с диапазонами (Range). Теперь для решения нашей задачи можно написать вот так:
static void Main()
{
var numbers = new int[] { 5, 1, 4, 2, 3, 7 };
var result = numbers[2..5];
Console.WriteLine(string.Join(" ", result)); // 4 2 3
}
То есть, для того чтобы получить некоторый диапазон из коллекции, нужно в квадратных скобках указать начальный индекс, затем .. и наконец индекс конца (!!! НЕ включительно !!!).
Например:
numbers[3..10] — вырезает элементы, начиная с индекса 3 и заканчивая индексом 9. Напоминаю, что правая граница не включается.
numbers[1..7] — вырезает элементы, начиная с индекса 1 и заканчивая индексом 6.
Если индексы будут равны между собой, то в результате получится массив нулевой длины:
static void Main()
{
var numbers = new int[] { 5, 1, 4, 2, 3, 7 };
var result = numbers[2..2];
Console.WriteLine(result.Length); // 0
}
Если первый индекс будет больше второго индекса, то возникнет исключение ArgumentOutOfRangeException во время выполнения программы:
static void Main()
{
var numbers = new int[] { 5, 1, 4, 2, 3, 7 };
var result = numbers[5..2]; // ArgumentOutOfRangeException
}
Можно использовать также индексацию справа налево (Indices), введенную тоже в C# 8 версии, про которую говорили совсем недавно [1]:
static void Main()
{
var numbers = new int[] { 5, 1, 4, 2, 3, 7 };
var result = numbers[^5..^2];
Console.WriteLine(string.Join(" ", result)); // 1 4 2
}
Можно делать еще более веселые штучки:
Например, для получения первых 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 (НЕ включительно).
Рассмотрим на примерах:
static void Main()
{
var numbers = new int[] { 5, 1, 4, 2, 3, 7 };
var range1 = new Range();
var result = numbers[range1]; // пустой массив
var range2 = new Range(2, 5);
result = numbers[range2]; // 4 2 3
var range3 = new Range(1, 3);
result = numbers[range3]; // 1 4
}
Заметьте, что объект типа Range передается в качестве индекса в квадратные скобки ([]).
Проведем соответствие между двумя разными записями:
|
Укороченная версия |
Версия с |
|---|---|
|
|
|
|
|
|
В Range реализовано неявное преобразование укороченной записи (например 2..5) к Range. Вот как это работает:
static void Main()
{
var numbers = new int[] { 5, 1, 4, 2, 3, 7 };
Range range = 2..5;
var result = numbers[range]; // 4 2 3
}
У Range переопределен метод Equals:
static void Main()
{
Range range1 = 2..5;
Range range2 = 2..5;
Range range3 = 1..6;
Console.WriteLine(range1.Equals(range2)); // True
Console.WriteLine(range1.Equals(range3)); // False
}
А можно вообще вот так:
static void Main()
{
Range range1 = 2..5;
Console.WriteLine(range1.Equals(2..5)); // True
Console.WriteLine(range1.Equals(1..6)); // False
}
Здесь сначала происходит неявное преобразование укороченной записи к Range, а потом вызов Equals.
У Range переопределен также метод ToString:
static void Main()
{
Range range1 = 2..5;
Range range2 = ^6..^3;
Console.WriteLine(range1.ToString()); // 2..5
Console.WriteLine(range2.ToString()); // ^6..^3
}
Заметьте, что для индексации с конца выводится ^ перед индексом.
Также теперь мы можем в методы передавать диапазон:
static void Test(int[] numbers, Range range)
{
// логика
}
Структура Range позволяет создать экземпляр, к которому можно обращаться многократно.
Код становится более короткий и читаемый.
Увеличивается производительность без обращения к лишним методам.
Меньше нагрузка на память.
PS. Написано с любовью вместе со своими учениками. Они у меня лучшие 😍
Автор: Дзеранов Иосиф
Источник [2]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/c-2/372074
Ссылки в тексте:
[1] недавно: https://habr.com/ru/post/649043/
[2] Источник: https://habr.com/ru/post/651171/?utm_source=habrahabr&utm_medium=rss&utm_campaign=651171
Нажмите здесь для печати.