Compartir a través de


Índices y intervalos

Los intervalos e índices proporcionan una sintaxis concisa para acceder a elementos o intervalos únicos en una secuencia.

En este tutorial, aprenderá a:

  • Use la sintaxis de los intervalos de una secuencia.
  • Defina implícitamente un Range.
  • Comprenda las decisiones de diseño para el inicio y el final de cada secuencia.
  • Aprenda sobre los escenarios para los tipos Index y Range.

Compatibilidad con idiomas para índices y intervalos

Los índices y intervalos proporcionan una sintaxis concisa para acceder a elementos o intervalos únicos en una secuencia.

Esta compatibilidad con lenguajes se basa en dos tipos nuevos y dos operadores nuevos:

  • System.Index representa un índice en una secuencia.
  • Índice del operador ^final , que especifica que un índice es relativo al final de una secuencia.
  • System.Range representa un sub intervalo de una secuencia.
  • Operador de intervalo .., que especifica el inicio y el final de un intervalo como sus operandos.

Comencemos con las reglas de índices. Considere una matriz sequence. El 0 índice es el mismo que sequence[0]. El ^0 índice es el mismo que sequence[sequence.Length]. La expresión sequence[^0] produce una excepción, igual sequence[sequence.Length] que lo hace. Para cualquier número n, el índice ^n es el mismo que sequence.Length - n.

private string[] words = [
                // index from start     index from end
    "first",    // 0                    ^10
    "second",   // 1                    ^9
    "third",    // 2                    ^8
    "fourth",   // 3                    ^7
    "fifth",    // 4                    ^6
    "sixth",    // 5                    ^5
    "seventh",  // 6                    ^4
    "eighth",   // 7                    ^3
    "ninth",    // 8                    ^2
    "tenth"     // 9                    ^1
];              // 10 (or words.Length) ^0

Puede recuperar la última palabra con el ^1 índice. Agregue el código siguiente debajo de la inicialización:

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

Un intervalo especifica el inicio y el final de un intervalo. El inicio del intervalo es inclusivo, pero el final del intervalo es exclusivo, lo que significa que el inicio se incluye en el intervalo, pero el final no está incluido en el intervalo. El intervalo [0..^0] representa todo el intervalo, igual que [0..sequence.Length] representa todo el intervalo.

El código siguiente crea un subrango con las palabras "second", "third" y "fourth". Incluye desde words[1] hasta words[3]. El elemento words[4] no está en el rango.

string[] secondThirdFourth = words[1..4]; // contains "second", "third" and "fourth"

// < second >< third >< fourth >
foreach (var word in secondThirdFourth)
    Console.Write($"< {word} >"); 
Console.WriteLine();

El siguiente código devuelve el rango con "noveno" y "décimo". Incluye words[^2] y words[^1]. No se incluye el índice words[^0] final.

 string[] lastTwo = words[^2..^0]; // contains "ninth" and "tenth"

 // < ninth >< tenth >
 foreach (var word in lastTwo)
     Console.Write($"< {word} >"); 
 Console.WriteLine();

En los ejemplos siguientes se crean intervalos que están abiertos para el inicio, el final o ambos:

string[] allWords = words[..]; // contains "first" through "tenth".
string[] firstPhrase = words[..4]; // contains "first" through "fourth"
string[] lastPhrase = words[6..]; // contains "seventh", "eight", "ninth" and "tenth"

// < first >< second >< third >< fourth >< fifth >< sixth >< seventh >< eighth >< ninth >< tenth >
foreach (var word in allWords)
    Console.Write($"< {word} >"); 
Console.WriteLine();

// < first >< second >< third >< fourth >
foreach (var word in firstPhrase)
    Console.Write($"< {word} >"); 
Console.WriteLine();

// < seventh >< eighth >< ninth >< tenth >
foreach (var word in lastPhrase)
    Console.Write($"< {word} >"); 
Console.WriteLine();

También puede declarar rangos o índices como variables. A continuación, la variable se puede usar dentro de los [ caracteres y ] :

Index thirdFromEnd = ^3;
Console.WriteLine($"< {words[thirdFromEnd]} > "); // < eighth > 
Range phrase = 1..4;
string[] text = words[phrase];

// < second >< third >< fourth >
foreach (var word in text)
    Console.Write($"< {word} >");  
Console.WriteLine();

En el ejemplo siguiente se muestran muchas de las razones de esas opciones. Modifique x, yy z para probar diferentes combinaciones. Al experimentar, use valores donde x es menor que yy y es menor que z para combinaciones válidas. Agregue el código siguiente en un nuevo método. Pruebe diferentes combinaciones:

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]}");

No solo las matrices admiten índices y intervalos. También puede usar índices y intervalos con cadena, Span<T>, o ReadOnlySpan<T>.

Conversiones implícitas de expresiones del operador de rango

Cuando se usa la sintaxis de expresión del operador de intervalo, el compilador convierte implícitamente los valores de inicio y final a un Index y a partir de ellos, crea una nueva instancia de Range. En el código siguiente se muestra un ejemplo de conversión implícita de la sintaxis de expresión del operador de rango y su alternativa explícita correspondiente.

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'

Importante

Las conversiones implícitas de Int32 a Index lanzan una ArgumentOutOfRangeException cuando el valor es negativo. Del mismo modo, el Index constructor lanza una ArgumentOutOfRangeException excepción cuando el value parámetro es negativo.

Soporte de tipos para índices y rangos

Los índices y intervalos proporcionan una sintaxis clara y concisa para acceder a un solo elemento o un intervalo de elementos de una secuencia. Normalmente, una expresión de índice devuelve el tipo de los elementos de una secuencia. Normalmente, una expresión de rango devuelve el mismo tipo de secuencia que la secuencia de origen.

Cualquier tipo que proporcione un indexador con un Index parámetro admitirá explícitamente índices, o con un Range admitirá intervalos, respectivamente. Un indexador que toma un único Range parámetro puede devolver un tipo de secuencia diferente, como System.Span<T>.

Importante

El rendimiento del código que usa el operador de intervalo depende del tipo del operando de secuencia.

La complejidad de tiempo del operador de intervalo depende del tipo de secuencia. Por ejemplo, si la secuencia es o string una matriz, el resultado es una copia de la sección especificada de la entrada, por lo que la complejidad del tiempo es O(N) (donde N es la longitud del intervalo). Por otro lado, si es un System.Span<T> o un System.Memory<T>, el resultado hace referencia al mismo almacenamiento principal, lo que significa que no hay ninguna copia y la operación es O(1).

Además de la complejidad del tiempo, esto provoca asignaciones y copias adicionales, lo que afecta al rendimiento. En el código sensible al rendimiento, considere la posibilidad de usar Span<T> o Memory<T> como tipo de secuencia, ya que el operador de intervalo no los asigna.

Un tipo es countable si tiene una propiedad denominada Length o Count con un captador accesible y un tipo de valor devuelto de int. Un tipo enumerable que no admite explícitamente índices o rangos podría proporcionar un soporte implícito para ellos. Para obtener más información, consulte las secciones Compatibilidad con índices implícitos y Compatibilidad con intervalos implícitos de la nota de propuesta de características. Los intervalos que utilizan soporte de rango implícito devuelven el mismo tipo de secuencia que la secuencia de origen.

Por ejemplo, los siguientes tipos de .NET admiten índices y intervalos: String, Span<T>y ReadOnlySpan<T>. List<T> admite índices, pero no admite intervalos.

Array tiene un comportamiento más matizado. Las matrices de dimensiones únicas admiten índices y intervalos. Las matrices multidimensionales no admiten indizadores ni intervalos. El indizador de una matriz multidimensional tiene varios parámetros, no un único parámetro. Las matrices escalonadas, también denominadas matriz de matrices, admiten intervalos e indizadores. En el ejemplo siguiente se muestra cómo iterar una subsección rectangular de una matriz escalonada. Itera la sección en el centro, excluyendo las tres primeras y últimas tres filas, y las dos primeras y últimas dos columnas de cada fila seleccionada:

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();
}

En todos los casos, el operador de intervalo para Array asigna una matriz para almacenar los elementos devueltos.

Escenarios para índices y intervalos

A menudo, usará intervalos e índices cuando desee analizar una parte de una secuencia mayor. La nueva sintaxis es más clara al leer exactamente qué parte de la secuencia está implicada. La función local MovingAverage toma Range como argumento. A continuación, el método enumera solo ese intervalo al calcular el mínimo, el máximo y el promedio. Pruebe el código siguiente en el proyecto:

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))];

Nota sobre índices y matrices de intervalos

Al tomar un intervalo de una matriz, el resultado es una matriz que se copia de la matriz inicial, en lugar de hacer referencia a esta. La modificación de valores en la matriz resultante no cambiará los valores de la matriz inicial.

Por ejemplo:

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

Consulte también