Bagikan melalui


Indeks dan rentang

Indeks dan rentang menyediakan sintaksis singkat untuk mengakses elemen atau rentang tunggal secara berurutan.

Dalam tutorial ini, Anda akan mempelajari cara:

  • Gunakan sintaks untuk rentang secara berurutan.
  • Secara implisit mendefinisikan Range.
  • Pahami keputusan desain untuk awal dan akhir setiap urutan.
  • Pelajari skenario untuk tipe Index dan Range.

Dukungan bahasa untuk indeks dan rentang

Indeks dan rentang menyediakan sintaksis singkat untuk mengakses elemen atau rentang tunggal secara berurutan.

Dukungan bahasa ini bergantung pada dua tipe baru dan dua operator baru:

  • System.Index mewakili indeks ke dalam urutan.
  • Indeks dari operator ^akhir , yang menentukan bahwa indeks relatif terhadap akhir urutan.
  • System.Range mewakili sub rentang urutan.
  • Operator ..rentang , yang menentukan awal dan akhir rentang sebagai operandnya.

Mari kita mulai dengan aturan untuk indeks. Pertimbangkan array sequence. Indeks 0 sama dengan sequence[0]. Indeks ^0 sama dengan sequence[sequence.Length]. Expression sequence[^0] melemparkan pengecualian seperti halnya sequence[sequence.Length]. Untuk angka n, indeks ^n selalu sama dengan sequence.Length - n.

string[] words = [
                // index from start    index from end
    "The",      // 0                   ^9
    "quick",    // 1                   ^8
    "brown",    // 2                   ^7
    "fox",      // 3                   ^6
    "jumps",    // 4                   ^5
    "over",     // 5                   ^4
    "the",      // 6                   ^3
    "lazy",     // 7                   ^2
    "dog"       // 8                   ^1
];              // 9 (or words.Length) ^0

Anda dapat mengambil kata terakhir dengan indeks ^1. Tambahkan kode berikut di bawah inisialisasi:

Console.WriteLine($"The last word is {words[^1]}");

Rentang menentukan awal dan akhir dari suatu rentang. Awal rentang bersifat inklusif, tetapi akhir rentang bersifat eksklusif, yang berarti awal disertakan dalam rentang tetapi akhir tidak disertakan dalam rentang. Rentang [0..^0] mewakili seluruh rentang, sama halnya dengan [0..sequence.Length] yang mewakili seluruh rentang.

Kode berikut membuat subrange dengan kata "quick", "brown", dan "fox". Ini termasuk words[1] melalui words[3]. Elemen words[4] tidak berada dalam rentang.

string[] quickBrownFox = words[1..4];
foreach (var word in quickBrownFox)
    Console.Write($"< {word} >");
Console.WriteLine();

Kode berikut mengembalikan rentang dengan "lazy" dan "dog". Ini termasuk words[^2] dan words[^1]. Indeks akhir words[^0] tidak disertakan. Tambahkan juga kode berikut:

string[] lazyDog = words[^2..^0];
foreach (var word in lazyDog)
    Console.Write($"< {word} >");
Console.WriteLine();

Contoh berikut membuat rentang yang terbuka untuk awal, akhir, atau keduanya:

string[] allWords = words[..]; // contains "The" through "dog".
string[] firstPhrase = words[..4]; // contains "The" through "fox"
string[] lastPhrase = words[6..]; // contains "the", "lazy" and "dog"
foreach (var word in allWords)
    Console.Write($"< {word} >");
Console.WriteLine();
foreach (var word in firstPhrase)
    Console.Write($"< {word} >");
Console.WriteLine();
foreach (var word in lastPhrase)
    Console.Write($"< {word} >");
Console.WriteLine();

Anda juga dapat mendeklarasikan rentang sebagai variabel. Rentang kemudian dapat digunakan di dalam karakter [ dan ]:

Index the = ^3;
Console.WriteLine(words[the]);
Range phrase = 1..4;
string[] text = words[phrase];
foreach (var word in text)
    Console.Write($"< {word} >");
Console.WriteLine();

Contoh berikut menunjukkan banyak alasan untuk pilihan tersebut. Ubah x, y, dan z untuk mencoba kombinasi yang berbeda. Saat Anda bereksperimen, gunakan nilai di mana x kurang dari y, dan y kurang dari z untuk kombinasi yang valid. Tambahkan kode berikut dalam method baru. Coba kombinasi yang berbeda:

int[] numbers = [..Enumerable.Range(0, 100)];
int x = 12;
int y = 25;
int z = 36;

Console.WriteLine($"{numbers[^x]} is the same as {numbers[numbers.Length - x]}");
Console.WriteLine($"{numbers[x..y].Length} is the same as {y - x}");

Console.WriteLine("numbers[x..y] and numbers[y..z] are consecutive and disjoint:");
Span<int> x_y = numbers[x..y];
Span<int> y_z = numbers[y..z];
Console.WriteLine($"\tnumbers[x..y] is {x_y[0]} through {x_y[^1]}, numbers[y..z] is {y_z[0]} through {y_z[^1]}");

Console.WriteLine("numbers[x..^x] removes x elements at each end:");
Span<int> x_x = numbers[x..^x];
Console.WriteLine($"\tnumbers[x..^x] starts with {x_x[0]} and ends with {x_x[^1]}");

Console.WriteLine("numbers[..x] means numbers[0..x] and numbers[x..] means numbers[x..^0]");
Span<int> start_x = numbers[..x];
Span<int> zero_x = numbers[0..x];
Console.WriteLine($"\t{start_x[0]}..{start_x[^1]} is the same as {zero_x[0]}..{zero_x[^1]}");
Span<int> z_end = numbers[z..];
Span<int> z_zero = numbers[z..^0];
Console.WriteLine($"\t{z_end[0]}..{z_end[^1]} is the same as {z_zero[0]}..{z_zero[^1]}");

Tidak hanya array yang mendukung indeks dan rentang. Anda juga dapat menggunakan indeks dan rentang dengan string, Span<T>, atau ReadOnlySpan<T>.

Konversi ekspresi operator rentang implisit

Saat menggunakan sintaks ekspresi operator rentang, pengkompilasi secara implisit mengonversi nilai awal dan akhir menjadi dan Index darinya, membuat instans baru Range . Kode berikut menunjukkan contoh konversi implisit dari sintaks ekspresi operator rentang, dan alternatif eksplisit yang sesuai:

Range implicitRange = 3..^5;

Range explicitRange = new(
    start: new Index(value: 3, fromEnd: false),
    end: new Index(value: 5, fromEnd: true));

if (implicitRange.Equals(explicitRange))
{
    Console.WriteLine(
        $"The implicit range '{implicitRange}' equals the explicit range '{explicitRange}'");
}
// Sample output:
//     The implicit range '3..^5' equals the explicit range '3..^5'

Penting

Konversi implisit dari Int32 ke Index melempar ArgumentOutOfRangeException ketika nilainya negatif. Demikian juga, Index konstruktor melempar ArgumentOutOfRangeException ketika value parameter negatif.

Dukungan tipe untuk indeks dan rentang

Indeks dan rentang menyediakan sintaks yang jelas dan ringkas untuk mengakses satu elemen atau rentang elemen secara berurutan. Expression indeks biasanya mengembalikan tipe elemen urutan. Expression rentang biasanya mengembalikan tipe urutan yang sama dengan urutan sumber.

Tipe apa pun yang menyediakan pengindeks dengan Index parameter atau Range secara eksplisit mendukung indeks atau rentang masing-masing. Pengindeks yang mengambil satu Range parameter dapat mengembalikan tipe urutan yang berbeda, seperti System.Span<T>.

Penting

Performa kode menggunakan operator rentang tergantung pada tipe operand urutan.

Kompleksitas waktu operator rentang tergantung pada tipe urutan. Misalnya, jika urutannya adalah string atau sebuah array, maka hasilnya adalah salinan dari bagian input yang ditentukan, sehingga kompleksitas waktu adalah O(N) (di mana N adalah panjang rentang). Di sisi lain, jika itu adalah System.Span<T> atau System.Memory<T>, hasilnya mereferensikan penyimpanan backing yang sama, yang berarti tidak ada salinan dan operasinya adalah O(1).

Selain kompleksitas waktu, ini menyebabkan alokasi dan salinan ekstra, yang berdampak pada performa. Dalam kode sensitif performa, pertimbangkan untuk menggunakan Span<T> atau Memory<T> sebagai tipe urutan, karena operator rentang tidak mengalokasikannya.

Tipe dapat dihitung jika memiliki properti bernama Length atau Count dengan getter yang dapat diakses dan tipe pengembalian int. Tipe yang dapat dihitung yang tidak secara eksplisit mendukung indeks atau rentang dapat memberikan dukungan implisit untuk tipe tersebut. Untuk informasi selengkapnya, lihat bagian dukungan Indeks Implisit dan dukungan Rentang Implisit dari catatan proposal fitur. Rentang yang menggunakan dukungan rentang implisit mengembalikan tipe urutan yang sama dengan urutan sumber.

Misalnya, jenis .NET berikut mendukung indeks dan rentang: String, Span<T>, dan ReadOnlySpan<T>. List<T> mendukung indeks tetapi tidak mendukung rentang.

Array memiliki perilaku yang lebih bervariasi. Array dimensi tunggal mendukung indeks dan rentang. Array multi-dimensi tidak mendukung pengindeks atau rentang. Pengindeks untuk array multi-dimensi memiliki beberapa parameter, bukan satu parameter. Jagged array, juga disebut sebagai array dari array, mendukung rentang dan pengindeks. Contoh berikut menunjukkan cara mengulangi sub bagian persegi panjang dari jagged array. Ini mengulang bagian di tengah, tidak termasuk tiga baris pertama dan terakhir, dan dua kolom pertama dan terakhir dari setiap baris yang dipilih:

int[][] jagged = 
[
   [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
   [10,11,12,13,14,15,16,17,18,19],
   [20,21,22,23,24,25,26,27,28,29],
   [30,31,32,33,34,35,36,37,38,39],
   [40,41,42,43,44,45,46,47,48,49],
   [50,51,52,53,54,55,56,57,58,59],
   [60,61,62,63,64,65,66,67,68,69],
   [70,71,72,73,74,75,76,77,78,79],
   [80,81,82,83,84,85,86,87,88,89],
   [90,91,92,93,94,95,96,97,98,99],
];

var selectedRows = jagged[3..^3];

foreach (var row in selectedRows)
{
    var selectedColumns = row[2..^2];
    foreach (var cell in selectedColumns)
    {
        Console.Write($"{cell}, ");
    }
    Console.WriteLine();
}

Dalam semua kasus, operator rentang untuk Array mengalokasikan array untuk menyimpan elemen yang dikembalikan.

Skenario untuk indeks dan rentang

Anda akan sering menggunakan rentang dan indeks saat ingin menganalisis sebagian urutan yang lebih besar. Sintaks baru lebih jelas dalam membaca dengan tepat bagian urutan apa yang terlibat. Fungsi MovingAverage lokal mengambil Range sebagai argumennya. Method ini kemudian menghitung hanya pada rentang itu saja saat menghitung nilai min, maks, dan rata-rata. Coba kode berikut di proyek Anda:

int[] sequence = Sequence(1000);

for(int start = 0; start < sequence.Length; start += 100)
{
    Range r = start..(start+10);
    var (min, max, average) = MovingAverage(sequence, r);
    Console.WriteLine($"From {r.Start} to {r.End}:    \tMin: {min},\tMax: {max},\tAverage: {average}");
}

for (int start = 0; start < sequence.Length; start += 100)
{
    Range r = ^(start + 10)..^start;
    var (min, max, average) = MovingAverage(sequence, r);
    Console.WriteLine($"From {r.Start} to {r.End}:  \tMin: {min},\tMax: {max},\tAverage: {average}");
}

(int min, int max, double average) MovingAverage(int[] subSequence, Range range) =>
    (
        subSequence[range].Min(),
        subSequence[range].Max(),
        subSequence[range].Average()
    );

int[] Sequence(int count) => [..Enumerable.Range(0, count).Select(x => (int)(Math.Sqrt(x) * 100))];

Catatan tentang Larik dan Indeks Rentang

Saat mengambil rentang dari larik, hasilnya adalah larik yang disalin dari larik awal, bukan yang direferensikan. Memodifikasi nilai dalam larik yang dihasilkan tidak akan mengubah nilai dalam larik awal.

Contohnya:

var arrayOfFiveItems = new[] { 1, 2, 3, 4, 5 };

var firstThreeItems = arrayOfFiveItems[..3]; // contains 1,2,3
firstThreeItems[0] =  11; // now contains 11,2,3

Console.WriteLine(string.Join(",", firstThreeItems));
Console.WriteLine(string.Join(",", arrayOfFiveItems));

// output:
// 11,2,3
// 1,2,3,4,5

Baca juga