Todo lo que le interesa sobre las matrices

Las matrices son una característica de lenguaje fundamental de la mayoría de los lenguajes de programación. Son una colección de valores u objetos que son difíciles de evitar. Vamos a profundizar en las matrices y en todo lo que tienen que ofrecer.

Nota

La versión original de este artículo apareció en el blog escrito por @KevinMarquette. El equipo de PowerShell agradece a Kevin que comparta este contenido con nosotros. Visite su blog en PowerShellExplained.com.

¿Qué es una matriz?

Voy a empezar con una descripción técnica básica sobre qué son las matrices y cómo se usan en la mayoría de los lenguajes de programación antes de pasar a las otras formas en que PowerShell las usa.

Una matriz es una estructura de datos que actúa como una colección de varios elementos. Puede iterar por la matriz o acceder a elementos individuales mediante un índice. La matriz se crea como un fragmento secuencial de memoria donde cada valor se almacena justo al lado del otro.

Hablaré sobre cada uno de los detalles a medida que avanzamos.

Uso básico

Dado que las matrices son una característica básica de PowerShell, existe una sintaxis sencilla para trabajar con ellas en PowerShell.

Creación de una matriz

Se puede crear una matriz vacía mediante @().

PS> $data = @()
PS> $data.count
0

Podemos crear una matriz e inicializarla con valores, con tan solo colocarlos dentro del paréntesis @().

PS> $data = @('Zero','One','Two','Three')
PS> $data.count
4

PS> $data
Zero
One
Two
Three

Esta matriz tiene cuatro elementos. Cuando llamamos a la variable $data, vemos la lista de nuestros elementos. Si es una matriz de cadenas, obtenemos una línea por cadena.

Se puede declarar una matriz en varias líneas. En este caso, la coma es opcional y, en general, se excluye.

$data = @(
    'Zero'
    'One'
    'Two'
    'Three'
)

Prefiero declarar mis matrices en varias líneas de esa forma. No solo es más fácil de leer cuando tiene varios elementos, sino que también facilita la comparación con versiones anteriores cuando se usa el control de código fuente.

Otra sintaxis

Normalmente, se entiende que @() es la sintaxis para crear una matriz, pero las listas separadas por comas funcionan en la mayoría de las ocasiones.

$data = 'Zero','One','Two','Three'

Write-Output para crear matrices

Un pequeño truco que merece la pena mencionar es que puede usar Write-Output para crear cadenas en la consola rápidamente.

$data = Write-Output Zero One Two Three

Esto resulta útil porque no es necesario colocar comillas alrededor de las cadenas cuando el parámetro acepta cadenas. Nunca haría esto en un script, pero se puede hacer en la consola.

Acceso a elementos

Ahora que tiene una matriz con elementos, puede acceder a ellos y actualizarlos.

Offset

Para acceder a elementos individuales, usamos los corchetes [] con un valor de desplazamiento que comienza en 0. Así es como obtenemos el primer elemento de la matriz:

PS> $data = 'Zero','One','Two','Three'
PS> $data[0]
Zero

El motivo por el que usamos cero aquí es porque el primer elemento está al principio de la lista, por lo que usamos un desplazamiento de 0 elementos para acceder a él. Para llegar al segundo elemento, es necesario usar un desplazamiento de 1 para omitir el primer elemento.

PS> $data[1]
One

Esto significa que el último elemento está en el desplazamiento 3.

PS> $data[3]
Three

Índice

Ahora puede ver por qué seleccioné esos valores para este ejemplo. He presentado esto como un desplazamiento porque eso es lo que realmente es, pero a este desplazamiento se le conoce de forma más común como un índice. Un índice que comienza en 0. En el resto de este artículo, utilizaremos el término índice para hacer referencia al desplazamiento.

Trucos especiales sobre índices

En la mayoría de los lenguajes, solo puede especificar un único número como el índice, y se obtendrá un solo elemento. PowerShell es mucho más flexible. Puede usar varios índices a la vez. Al proporcionar una lista de índices, podemos seleccionar varios elementos.

PS> $data[0,2,3]
Zero
Two
Three

Los elementos se devuelven según el orden de los índices proporcionados. Si duplica un índice, obtendrá ese elemento las dos veces.

PS> $data[3,0,3]
Three
Zero
Three

Podemos especificar una secuencia de números con el operador .. integrado.

PS> $data[1..3]
One
Two
Three

Esto también funciona en orden inverso.

PS> $data[3..1]
Three
Two
One

Puede usar valores de índice negativos para desplazarse desde el final. Por lo tanto, si necesita el último elemento de la lista, puede usar -1.

PS> $data[-1]
Three

Hay que tener cuidado aquí con el operador ... Las secuencias 0..-1 y -1..0 dan como resultado los valores 0,-1 y -1,0. Es fácil ver $data[0..-1] y pensar que enumeraría todos los elementos si olvida este detalle. $data[0..-1] proporciona el mismo valor que $data[0,-1] al proporcionar el primer y último elemento de la matriz, y ninguno de los demás valores. Este es un ejemplo más grande:

PS> $a = 1,2,3,4,5,6,7,8
PS> $a[2..-1]
3
2
1
8

Esto es igual que:

PS> $a[2,1,0,-1]
3
2
1
8

Fuera de los límites

En la mayoría de los lenguajes, si intenta acceder a un índice de un elemento que va más allá del final de la matriz, obtendría algún tipo de error o una excepción. PowerShell no devuelve nada de forma silenciosa.

PS> $null -eq $data[9000]
True

No se puede indexar en una matriz NULL

Si la variable es $null e intenta indexarla como una matriz, obtendrá una excepción System.Management.Automation.RuntimeException con el mensaje Cannot index into a null array.

PS> $empty = $null
PS> $empty[0]
Error: Cannot index into a null array.

Por lo tanto, asegúrese de que las matrices no son $null antes de intentar acceder a los elementos que contienen.

Count

Las matrices y otras colecciones tienen una propiedad Count que indica cuántos elementos hay en la matriz.

PS> $data.count
4

PowerShell 3.0 agregó una propiedad Count a la mayoría de los objetos. Puede tener un solo objeto y debe proporcionar un recuento de 1.

PS> $date = Get-Date
PS> $date.count
1

Incluso $null tiene una propiedad Count, salvo que devuelve 0.

PS> $null.count
0

Hay algunas excepciones aquí que revisaré cuando trate la búsqueda de $null o matrices vacías más adelante en este artículo.

Errores puntuales

Se crea un error de programación común porque las matrices empiezan en el índice 0. Los errores puntuales pueden introducirse de dos maneras.

Lo primero sería pensar que desea el segundo elemento y usa un índice de 2 y obtiene realmente el tercer elemento. O bien, piense que tiene cuatro elementos y desea el último elemento, por lo que usa el recuento para acceder al último elemento.

$data[ $data.count ]

PowerShell puede permitirle hacer eso y proporcionar exactamente el elemento que existe en el índice 4: $null. Debe usar $data.count - 1 o -1, dos parámetros sobre los que hemos obtenido información anteriormente.

PS> $data[ $data.count - 1 ]
Three

Aquí es donde puede usar el índice -1 para obtener el último elemento.

PS> $data[ -1 ]
Three

Lee Dailey también me dijo que podemos usar $data.GetUpperBound(0) para obtener el número de índice máximo.

PS> $data.GetUpperBound(0)
3
PS> $data[ $data.GetUpperBound(0) ]
Three

La segunda forma más común es al iterar la lista y no detenerse en el momento adecuado. Lo revisaré cuando hablemos sobre el uso del bucle for.

Actualización de elementos

Podemos usar el mismo índice para actualizar los elementos existentes en la matriz. Esto nos proporciona acceso directo para actualizar elementos individuales.

$data[2] = 'dos'
$data[3] = 'tres'

Si se intenta actualizar un elemento que está más allá del último elemento, se obtiene un error Index was outside the bounds of the array..

PS> $data[4] = 'four'
Index was outside the bounds of the array.
At line:1 char:1
+ $data[4] = 'four'
+ ~~~~~~~~~~~~~
+ CategoryInfo          : OperationStopped: (:) [], IndexOutOfRangeException
+ FullyQualifiedErrorId : System.IndexOutOfRangeException

Revisaré esto más adelante cuando hable sobre cómo aumentar una matriz.

Iteración

En el algún momento, puede que necesite recorrer la lista completa o iterar por ella para realizar alguna acción en cada elemento de la matriz.

Canalización

Las matrices y la canalización de PowerShell están diseñadas para complementarse. Esta es una de las formas más sencillas de procesar dichos valores. Al pasar una matriz a una canalización, cada elemento contenido en la matriz se procesa individualmente.

PS> $data = 'Zero','One','Two','Three'
PS> $data | ForEach-Object {"Item: [$PSItem]"}
Item: [Zero]
Item: [One]
Item: [Two]
Item: [Three]

Si no ha visto $PSItem antes, solo tiene que saber que es lo mismo que $_. Puede usar cualquiera de ellos, ya que ambos representan el objeto actual de la canalización.

Bucle forEach

El bucle ForEach funciona bien con las colecciones. Uso de la sintaxis: foreach ( <variable> in <collection> )

foreach ( $node in $data )
{
    "Item: [$node]"
}

Método forEach

Tiendo a olvidarme de él, pero funciona bien con operaciones sencillas. PowerShell le permite llamar a .ForEach() en una colección.

PS> $data.foreach({"Item [$PSItem]"})
Item [Zero]
Item [One]
Item [Two]
Item [Three]

.foreach() adopta un parámetro que es un bloque de script. Puede quitar los paréntesis y simplemente proporcionar el bloque de script.

$data.foreach{"Item [$PSItem]"}

Se trata de una sintaxis menos conocida, pero funciona exactamente igual. Este método foreach se agregó en PowerShell 4.0.

Bucle For

El bucle for se usa mucho en muchos de los otros lenguajes, pero no se ve con frecuencia en PowerShell. Cuando lo vea, suele ser en el contexto de recorrer una matriz.

for ( $index = 0; $index -lt $data.count; $index++)
{
    "Item: [{0}]" -f $data[$index]
}

Lo primero que hacemos es inicializar $index en 0. A continuación, agregamos la condición de que $index debe ser inferior a $data.count. Por último, se especifica que cada vez que se cree un bucle, el índice se debe aumentar en 1. En este caso, $index++ es la forma abreviada de $index = $index + 1. El operador de formato (-f) se usa para insertar el valor de $data[$index] en la cadena de salida.

Siempre que use un bucle for, preste especial atención a la condición. He usado $index -lt $data.count aquí. Es fácil obtener una condición algo incorrecta para obtener un error puntual en la lógica. El uso de $index -le $data.count o $index -lt ($data.count - 1) siempre suele generar algún error. Esto haría que el resultado procesara demasiados o muy pocos elementos. Este es el típico error puntual.

Bucle switch

Es fácil pasarlo por alto. Si proporciona una matriz a una instrucción switch, esta comprueba cada elemento de la matriz.

$data = 'Zero','One','Two','Three'
switch( $data )
{
    'One'
    {
        'Tock'
    }
    'Three'
    {
        'Tock'
    }
    Default
    {
        'Tick'
    }
}
Tick
Tock
Tick
Tock

Hay muchas cosas interesantes que podemos hacer con la instrucción switch. Tengo otro artículo dedicado a esto.

Actualización de valores

Cuando la matriz es una colección de cadenas o enteros (tipos de valor), puede que a veces desee actualizar los valores de la matriz a medida que se recorren en bucle. La mayoría de los bucles anteriores usan una variable en el bucle que contiene una copia del valor. Si actualiza esa variable, no se actualiza el valor original de la matriz.

La excepción a esa instrucción es el bucle for. Si desea recorrer una matriz y actualizar los valores que contiene, el bucle for es lo que busca.

for ( $index = 0; $index -lt $data.count; $index++ )
{
    $data[$index] = "Item: [{0}]" -f $data[$index]
}

En este ejemplo, se adopta un valor para el índice, se realizan algunos cambios y, después, se usa ese mismo índice para volver a asignarlo.

Matrices de objetos

Hasta ahora, lo único que hemos colocado en una matriz es un tipo de valor, pero las matrices también pueden contener objetos.

$data = @(
    [pscustomobject]@{FirstName='Kevin';LastName='Marquette'}
    [pscustomobject]@{FirstName='John'; LastName='Doe'}
)

Muchos cmdlets devuelven colecciones de objetos como matrices cuando se asignan a una variable.

$processList = Get-Process

Todas las características básicas de las que ya se ha hablado también se aplican a las matrices de objetos con algunos detalles que merece la pena destacar.

Acceso a las propiedades

Se puede usar un índice para acceder a un elemento individual de una colección, de la misma forma que con los tipos de valor.

PS> $data[0]

FirstName LastName
-----     ----
Kevin     Marquette

Se puede acceder a las propiedades y actualizarlas directamente.

PS> $data[0].FirstName

Kevin

PS> $data[0].FirstName = 'Jay'
PS> $data[0]

FirstName LastName
-----     ----
Jay       Marquette

Propiedades de las matrices

Normalmente, tendría que enumerar la lista completa de la siguiente forma para acceder a todas las propiedades:

PS> $data | ForEach-Object {$_.LastName}

Marquette
Doe

También puede usar el cmdlet Select-Object -ExpandProperty.

PS> $data | Select-Object -ExpandProperty LastName

Marquette
Doe

Pero PowerShell nos ofrece la posibilidad de solicitar LastName directamente. PowerShell realiza la enumeración automáticamente y devuelve una lista limpia.

PS> $data.LastName

Marquette
Doe

Aún así, se realiza la enumeración, pero no vemos la complejidad subyacente.

Filtrado con Where-Object

Aquí es donde Where-Object entra en acción, de tal forma que se permite filtrar y seleccionar lo que se desee fuera de la matriz en función de las propiedades del objeto.

PS> $data | Where-Object {$_.FirstName -eq 'Kevin'}

FirstName LastName
-----     ----
Kevin     Marquette

Se puede escribir esa misma consulta para obtener el elemento FirstName que se busca.

$data | Where FirstName -eq Kevin

Where()

Las matrices tienen un método Where() que permite especificar un elemento scriptblock para el filtro.

$data.Where({$_.FirstName -eq 'Kevin'})

Esta característica se agregó en PowerShell 4.0.

Actualización de objetos en bucles

Con los tipos de valor, la única manera de actualizar la matriz es usar un bucle for, porque es necesario conocer el índice para reemplazar el valor. Hay más opciones con objetos porque son tipos de referencia. Este es un ejemplo rápido:

foreach($person in $data)
{
    $person.FirstName = 'Kevin'
}

Este bucle recorre todos los objetos de la matriz $data. Dado que los objetos son tipos de referencia, la variable $person hace referencia al mismo objeto que se encuentra en la matriz. Por tanto, las actualizaciones de sus propiedades actualizan el original.

Todavía no se puede reemplazar todo el objeto de esta manera. Si se intenta asignar un nuevo objeto a la variable $person, se actualiza la referencia de variable a otro elemento que ya no apunta al objeto original de la matriz. Esto no funciona como cabría esperar:

foreach($person in $data)
{
    $person = [pscustomobject]@{
        FirstName='Kevin'
        LastName='Marquette'
    }
}

Operadores

Los operadores de PowerShell también sirven para las matrices. Algunos de ellos funcionan de manera ligeramente diferente.

-join

El operador -join es el más obvio, por lo que vamos a profundizar en él primero. Me gusta el operador -join y lo uso con frecuencia. Combina todos los elementos de la matriz con el carácter o la cadena que se especifique.

PS> $data = @(1,2,3,4)
PS> $data -join '-'
1-2-3-4
PS> $data -join ','
1,2,3,4

Una de las características que me gustan del operador -join es que controla elementos individuales.

PS> 1 -join '-'
1

Lo utilizo dentro de registros y mensajes detallados.

PS> $data = @(1,2,3,4)
PS> "Data is $($data -join ',')."
Data is 1,2,3,4.

-join $array

Este es un truco inteligente que Lee Dailey me indicó. Si alguna vez se desea combinar todo sin un delimitador, en lugar de hacer esto:

PS> $data = @(1,2,3,4)
PS> $data -join $null
1234

Se puede usar -join con la matriz como parámetro sin prefijo. Eche un vistazo a este ejemplo para saber de qué estoy hablando.

PS> $data = @(1,2,3,4)
PS> -join $data
1234

-replace y -split

Los otros operadores, como -replace y -split, se ejecutan en cada elemento de la matriz. No puedo decir que siempre lo he utilizado de esta forma, pero aquí se muestra un ejemplo.

PS> $data = @('ATX-SQL-01','ATX-SQL-02','ATX-SQL-03')
PS> $data -replace 'ATX','LAX'
LAX-SQL-01
LAX-SQL-02
LAX-SQL-03

-contains

El operador -contains permite comprobar una matriz de valores para ver si contiene un valor especificado.

PS> $data = @('red','green','blue')
PS> $data -contains 'green'
True

-in

Si tiene un valor único que le gustaría comprobar si coincide con alguno de varios valores, puede usar el operador -in. El valor se encontraría a la izquierda y la matriz en el lado derecho del operador.

PS> $data = @('red','green','blue')
PS> 'green' -in $data
True

Esto puede resultar complicado si la lista es grande. A menudo, utilizo un patrón regex si voy a comprobar más de unos pocos valores.

PS> $data = @('red','green','blue')
PS> $pattern = "^({0})$" -f ($data -join '|')
PS> $pattern
^(red|green|blue)$

PS> 'green' -match $pattern
True

-eq y -ne

La igualdad con las matrices puede resultar complicada. Cuando la matriz está en el lado izquierdo, se compara cada elemento. En lugar de devolver True, se devuelve el objeto con el que existe una coincidencia.

PS> $data = @('red','green','blue')
PS> $data -eq 'green'
green

Cuando se usa el operador -ne, se obtienen todos los valores que no son iguales a nuestro valor.

PS> $data = @('red','green','blue')
PS> $data -ne 'green'
red
blue

Cuando se usa esto en una instrucción if(), el valor devuelto es True. Si no se devuelve ningún valor, se trata de un valor False. En ambos casos, las instrucciones siguientes se evalúan como True.

$data = @('red','green','blue')
if ( $data -eq 'green' )
{
    'Green was found'
}
if ( $data -ne 'green' )
{
    'And green was not found'
}

Examinaré esto cuando hablemos de las pruebas de $null.

-match

El operador -match intenta coincidir con cada elemento de la colección.

PS> $servers = @(
    'LAX-SQL-01'
    'LAX-API-01'
    'ATX-SQL-01'
    'ATX-API-01'
)
PS> $servers -match 'SQL'
LAX-SQL-01
ATX-SQL-01

Cuando se usa -match con un solo valor, una variable especial $Matches se rellena con información de coincidencia. Esto no sucede cuando una matriz se procesa de esta manera.

Se puede adoptar el mismo enfoque con Select-String.

$servers | Select-String SQL

Vamos a profundizar más sobre Select-String, -match y la variable $matches en otra publicación titulada Las distintas formas de usar regex.

$null o valores vacíos

La prueba de $null o de matrices vacías puede resultar complicada. Estas son las excepciones comunes con las matrices.

A primera vista, parece que esta instrucción debería funcionar.

if ( $array -eq $null)
{
    'Array is $null'
}

Sin embargo, acabo de observar cómo -eq comprueba cada elemento de la matriz. Por tanto, puede haber una matriz de varios elementos con un solo valor $null, que se evaluaría como $true

$array = @('one',$null,'three')
if ( $array -eq $null)
{
    'I think Array is $null, but I would be wrong'
}

Esta es la razón por la que se recomienda colocar $null a la izquierda del operador. Esto hace que este escenario no plantee problemas.

if ( $null -eq $array )
{
    'Array actually is $null'
}

Una matriz $null no es lo mismo que una matriz vacía. Si se sabe que existe una matriz, compruebe el número de objetos que contiene. Si la matriz es $null, el recuento es 0.

if ( $array.count -gt 0 )
{
    "Array isn't empty"
}

Hay otra excepción que hay que tener en cuenta aquí. Se puede usar count incluso si tiene un solo objeto, a menos que ese objeto sea PSCustomObject. Se trata de un error que se corrigió en PowerShell 6.1. Es algo positivo, pero hay muchas personas que aún usan la versión 5.1 y, por tanto, es necesario tenerlo en cuenta.

PS> $object = [PSCustomObject]@{Name='TestObject'}
PS> $object.count
$null

Si todavía usa PowerShell 5.1, puede encapsular el objeto en una matriz antes de comprobar el recuento para obtener un número preciso.

if ( @($array).count -gt 0 )
{
    "Array isn't empty"
}

Para hacerlo con seguridad, compruebe $null y, después, el recuento.

if ( $null -ne $array -and @($array).count -gt 0 )
{
    "Array isn't empty"
}

All -eq

Recientemente observé que alguien preguntó cómo comprobar que todos los valores de una matriz coinciden con un valor determinado. El usuario de Reddit /u/bis tenía esta solución inteligente que comprueba si hay valores incorrectos y, después, cambia el resultado.

$results = Test-Something
if ( -not ( $results -ne 'Passed') )
{
    'All results a Passed'
}

Adición a matrices

Llegados a este punto, se empieza a preguntar cómo agregar elementos a una matriz. La respuesta rápida es que no se puede. Una matriz tiene un tamaño de memoria fijo. Si necesita aumentarlo o agregar un solo elemento a ella, se debe crear una matriz y copiar todos los valores de la matriz anterior. Esto suena trabajoso, pero PowerShell elimina la complejidad de crear la matriz. PowerShell implementa el operador de suma (+) para las matrices.

Nota

PowerShell no implementa una operación de resta. Si quiere una alternativa flexible a una matriz, debe usar un objeto List genérico.

Adición de matrices

Se puede usar el operador de suma con matrices para crear una matriz. Por lo tanto, dadas estas dos matrices:

$first = @(
    'Zero'
    'One'
)
$second = @(
    'Two'
    'Three'
)

Se pueden agregar juntas para obtener una nueva matriz.

PS> $first + $second

Zero
One
Two
Three

Signos más e igual (+=)

Se puede crear una matriz en contexto y agregarle un elemento similar al siguiente:

$data = @(
    'Zero'
    'One'
    'Two'
    'Three'
)
$data += 'four'

Recuerde que cada vez que se use +=, se duplica y crea una matriz. Esto no es un problema para los conjuntos de datos pequeños, pero el escalado es muy deficiente.

Asignación de canalización

Se pueden asignar los resultados de cualquier canalización en una variable. Se trata de una matriz si contiene varios elementos.

$array = 1..5 | ForEach-Object {
    "ATX-SQL-$PSItem"
}

Normalmente, al pensar en el uso de la canalización, viene a la mente la típica integración de PowerShell. Se puede utilizar la canalización con instrucciones foreach() y otros bucles. Por lo tanto, en lugar de agregar elementos a una matriz en un bucle, se pueden colocar elementos en la canalización.

$array = foreach ( $node in (1..5))
{
    "ATX-SQL-$node"
}

Tipos de matriz

De forma predeterminada, una matriz de PowerShell se crea como un tipo [PSObject[]]. Esto permite que contenga cualquier tipo de objeto o valor. Esto funciona porque todo se hereda del tipo PSObject.

Matrices fuertemente tipadas

Se puede crear una matriz de cualquier tipo con una sintaxis similar. Cuando se crea una matriz fuertemente tipada, solo puede contener valores u objetos del tipo especificado.

PS> [int[]] $numbers = 1,2,3
PS> [int[]] $numbers2 = 'one','two','three'
ERROR: Cannot convert value "one" to type "System.Int32". Input string was not in a correct format."

PS> [string[]] $strings = 'one','two','three'

ArrayList

Agregar elementos a una matriz es una de sus mayores limitaciones, pero hay algunas otras colecciones que se pueden usar para solucionar este problema.

ArrayList suele ser una de las primeras soluciones a las que se recurre cuando se necesita una matriz con la que se pueda trabajar más rápido. Actúa como una matriz de objetos cada vez que se necesita, pero puede agregar los elementos con rapidez.

Aquí se muestra cómo se crea un elemento ArrayList y cómo se le agregan elementos.

$myarray = [System.Collections.ArrayList]::new()
[void]$myArray.Add('Value')

Se recurre a .NET para obtener este tipo. En este caso, se usa el constructor predeterminado para crearlo. A continuación, se llama al método Add para agregarle un elemento.

La razón por la que uso [void] al principio de la línea es para suprimir el código de retorno. Algunas llamadas a .NET permiten hacer esto y pueden crear resultados inesperados.

Si los únicos datos que hay en la matriz son cadenas, eche un vistazo al uso de StringBuilder. Es prácticamente lo mismo, pero tiene algunos métodos que solo se utilizan para tratar cadenas. StringBuilder está diseñado especialmente para el rendimiento.

Es habitual que las personas pasen a ArrayList desde las matrices. Pero esto pertenece a una época en la que C# no contaba con compatibilidad genérica. La compatibilidad de ArrayList con el elemento List[] genérico ha quedado en desuso.

Lista genérica

Un tipo genérico es un tipo especial de C# que define una clase generalizada, y el usuario especifica los tipos de datos que usa cuando se crea. Por lo tanto, si se desea una lista de números o cadenas, hay que especificar que se desea una lista de tipos int o string.

Aquí se muestra cómo crear una lista de cadenas.

$mylist = [System.Collections.Generic.List[string]]::new()

También podría ser una lista de números.

$mylist = [System.Collections.Generic.List[int]]::new()

Se puede convertir una matriz existente en una lista como esta sin crear primero el objeto:

$mylist = [System.Collections.Generic.List[int]]@(1,2,3)

Se puede acortar la sintaxis con la instrucción using namespace en PowerShell 5 y versiones posteriores. La instrucción using debe ser la primera línea del script. Al declarar un espacio de nombres, PowerShell permite dejarlo fuera de los tipos de datos cuando se hace referencia a ellos.

using namespace System.Collections.Generic
$myList = [List[int]]@(1,2,3)

Esto hace que List sea mucho más fácil de usar.

Tiene un método Add similar a su disposición. A diferencia de ArrayList, no hay ningún valor devuelto en el método Add, por lo que no tenemos que aplicarle el elemento void.

$myList.Add(10)

De esta forma, aún se puede acceder a los elementos como otras matrices.

PS> $myList[-1]
10

List[PSObject]

Puede haber una lista de cualquier tipo, pero cuando no se conozca el tipo de objetos, se puede usar [List[PSObject]] para contenerlos.

$list = [List[PSObject]]::new()

Remove()

ArrayList y el elemento List[] genérico admiten la eliminación de elementos de la colección.

using namespace System.Collections.Generic
$myList = [List[string]]@('Zero','One','Two','Three')
[void]$myList.Remove("Two")
Zero
One
Three

Cuando se trabaja con tipos de valor, se quita el primero de la lista. Puede llamarlo una y otra vez para seguir quitando ese valor. Si se tienen tipos de referencia, se debe proporcionar el objeto que se desea quitar.

[list[System.Management.Automation.PSDriveInfo]]$drives = Get-PSDrive
$drives.remove($drives[2])
$delete = $drives[2]
$drives.remove($delete)

El método Remove devuelve true si pudo encontrar y quitar el elemento de la colección.

Más colecciones

Hay muchas otras colecciones que se pueden usar, pero estas son las sustituciones de matrices genéricas adecuadas. Si le interesa obtener más información sobre estas opciones, eche un vistazo a este gist que Mark Kraus ha organizado.

Otros matices

Ahora que he tratado toda la funcionalidad principal, aquí hay algunas cuestiones más que quería mencionar antes de terminar.

Matrices con un tamaño establecido previamente

He mencionado que no se puede cambiar el tamaño de una matriz una vez creada. Se puede crear una matriz de un tamaño determinado previamente si se le llama con el constructor new($size).

$data = [Object[]]::new(4)
$data.count
4

Multiplicación de matrices

Un truquito interesante es que se puede multiplicar una matriz por un entero.

PS> $data = @('red','green','blue')
PS> $data * 3
red
green
blue
red
green
blue
red
green
blue

Inicialización con 0

Un escenario común es la intención de crear una matriz que contenga solo ceros. Si solo va a haber enteros, una matriz fuertemente tipada de enteros, de forma predeterminada, solo contendrá ceros.

PS> [int[]]::new(4)
0
0
0
0

Se puede utilizar el truco de la multiplicación para hacer esto.

PS> $data = @(0) * 4
PS> $data
0
0
0
0

Lo bueno del truco de la multiplicación es que se puede usar cualquier valor. Por lo tanto, si se prefiere tener 255 como valor predeterminado, sería una buena forma de hacerlo.

PS> $data = @(255) * 4
PS> $data
255
255
255
255

Matrices anidadas

Una matriz dentro de una matriz se denomina matriz anidada. No la utilizo mucho en PowerShell, pero sí la he usado más en otros lenguajes. Considere la posibilidad de usar una matriz de matrices cuando los datos se ajusten a un patrón tipo cuadrícula.

A continuación, se muestran dos formas de crear una matriz bidimensional.

$data = @(@(1,2,3),@(4,5,6),@(7,8,9))

$data2 = @(
    @(1,2,3),
    @(4,5,6),
    @(7,8,9)
)

La coma es muy importante en esos ejemplos. Proporcioné un ejemplo anterior de una matriz normal en varias líneas donde la coma era opcional. Esto no sucede con una matriz multidimensional.

La forma en que se usa la notación de índice cambia ligeramente ahora que hay una matriz anidada. Con el elemento $data anterior, esta es la forma en que se accedería al valor 3.

PS> $outside = 0
PS> $inside = 2
PS> $data[$outside][$inside]
3

Agregue un par de corchetes para cada nivel de anidamiento de matriz. El primer par de corchetes es para la matriz más externa y, después, empiece a trabajar a partir de ahí.

Write-Output -NoEnumerate

En PowerShell, se suelen desajustar o enumerar las matrices. Este es un aspecto fundamental de la forma en que PowerShell usa la canalización, pero hay ocasiones en las que no se desea que esto sea así.

Normalmente, los objetos se canalizan hacia Get-Member para obtener más información sobre ellos. Al canalizar una matriz hacia ahí, se desajusta, y Get-Member ve los miembros de la matriz y no la matriz real.

PS> $data = @('red','green','blue')
PS> $data | Get-Member
TypeName: System.String
...

Para evitar que se desajuste la matriz, se puede usar Write-Output -NoEnumerate.

PS> Write-Output -NoEnumerate $data | Get-Member
TypeName: System.Object[]
...

Existe una segunda forma que es más que un truco, y trato de evitar trucos como este. Se puede colocar una coma delante de la matriz antes de canalizarla. Esto encapsula a $data en otra matriz donde es el único elemento, por lo que después de desencapsular la matriz externa, $data se vuelve a desencapsular.

PS> ,$data | Get-Member
TypeName: System.Object[]
...

Devolución de una matriz

Este desajuste de matrices también se produce cuando se generan o se devuelven valores a partir de una función. También se puede obtener una matriz si se asigna la salida a una variable, por lo que esto no suele ser un problema.

El problema es que hay una nueva matriz. Si surge algún problema, puede usar Write-Output -NoEnumerate $array o return ,$array para solucionarlo.

¿Algo más?

Sé que esto es mucho para asimilar. Espero que aprenda algo de este artículo cada vez que lo lea y que se convierta en una buena referencia a la que recurrir a largo plazo. Si le ha resultado útil, compártalo con otras personas a las que crea que puede servir.

Ahora, recomendaría consultar una publicación similar que escribí sobre las tablas hash.