Инициализация массива PowerShell - PullRequest
69 голосов
/ 22 октября 2008

Каков наилучший способ инициализации массива в PowerShell?

Например, код

$array = @()
for($i=0; $i -lt 5;$i++)
{
    $array[$i] = $FALSE
}

генерирует ошибку

Array assignment failed because index '0' was out of range.
At H:\Software\PowerShell\TestArray.ps1:4 char:10
+         $array[$ <<<< i] = $FALSE

Ответы [ 10 ]

86 голосов
/ 24 октября 2008

Вот еще два способа, оба очень лаконичны.

$arr1 = @(0) * 20
$arr2 = ,0 * 20
50 голосов
/ 22 октября 2008

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

> $a = new-object bool[] 5
> $a
False
False
False
False
False

Значение по умолчанию bool , по-видимому, false , так что это работает в вашем случае. Аналогично, если вы создадите типизированный массив int [] , вы получите значение по умолчанию, равное 0.

Еще один крутой способ инициализации массивов - это сокращение:

> $a = ($false, $false, $false, $false, $false)
> $a
False
False
False
False
False

Или, если хотите, вы хотите инициализировать диапазон, я иногда находил это полезным:

> $a = (1..5)   
> $a
1
2
3
4
5

Надеюсь, это было несколько полезно!

42 голосов
/ 22 октября 2008

Еще одна альтернатива:

for ($i = 0; $i -lt 5; $i++) 
{ 
  $arr += @($false) 
}

Этот работает, если $ arr еще не определен.

ПРИМЕЧАНИЕ - есть лучшие (и более эффективные) способы сделать это ... см. https://stackoverflow.com/a/234060/4570 ниже в качестве примера.

35 голосов
/ 19 января 2015

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

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

Здесь я использую Measure-Command, чтобы узнать, сколько времени занимает каждая инициализация. Как вы можете догадаться, любой подход, использующий явный цикл PowerShell, работает медленнее, чем тот, который использует конструкторы .Net или операторы PowerShell (которые будут скомпилированы в IL или в собственном коде).

Резюме

  • New-Object и @(somevalue)*n быстрые (около 20 тыс. Тиков на 100 тыс. Элементов).
  • Создание массива с оператором диапазона n..m медленнее в 10 раз (200 тыс. Тиков).
  • Использование ArrayList с методом Add() в 1000 раз медленнее, чем базовая линия (20M тиков), как и при циклическом просмотре массива уже размера с использованием for() или ForEach-Object (он же foreach, %) ,
  • Добавление с += является наихудшим (2M тиков для всего 1000 элементов).

В целом, я бы сказал, массив * n "лучший", потому что:

  • Это быстро.
  • Вы можете использовать любое значение, а не только значение по умолчанию для типа.
  • Вы можете создавать повторяющиеся значения (для иллюстрации введите это в командной строке powershell: (1..10)*10 -join " " или ('one',2,3)*3)
  • Краткий синтаксис.

Единственный недостаток:

  • Неочевидный. Если вы раньше не видели эту конструкцию, неясно, что она делает.

Но имейте в виду, что во многих случаях, когда вы хотите инициализировать элементы массива некоторым значением, массив со строгой типизацией - это именно то, что вам нужно. Если вы инициализируете все в $false, то будет ли когда-нибудь массив содержать что-то кроме $false или $true? Если нет, то New-Object type[] n - «лучший» подход.

Тестирование

Создайте и измените размер массива по умолчанию, затем присвойте значения:

PS> Measure-Command -Expression {$a = new-object object[] 100000} | Format-List -Property "Ticks"
Ticks : 20039

PS> Measure-Command -Expression {for($i=0; $i -lt $a.Length;$i++) {$a[$i] = $false}} | Format-List -Property "Ticks"
Ticks : 28866028

Создание массива Boolean немного медленнее, чем массив Object:

PS> Measure-Command -Expression {$a = New-Object bool[] 100000} | Format-List -Property "Ticks"
Ticks : 130968

Не очевидно, что это делает, в документации для New-Object просто говорится, что вторым параметром является список аргументов, который передается конструктору объектов .Net. В случае массивов параметр, очевидно, является желаемым размером.

Добавление с + =

PS> $a=@()
PS> Measure-Command -Expression { for ($i=0; $i -lt 100000; $i++) {$a+=$false} } | Format-List -Property "Ticks"

Я устал ждать, пока это завершится, поэтому Ctrl + C тогда:

PS> $a=@()
PS> Measure-Command -Expression { for ($i=0; $i -lt    100; $i++) {$a+=$false} } | Format-List -Property "Ticks"
Ticks : 147663
PS> $a=@()
PS> Measure-Command -Expression { for ($i=0; $i -lt   1000; $i++) {$a+=$false} } | Format-List -Property "Ticks"
Ticks : 2194398

Так же, как (6 * 3) концептуально похож на (6 + 6 + 6), так ($ somearray * 3) должен дать тот же результат, что и ($ somearray + $ somearray + $ somearray). Но с массивами + - это конкатенация, а не сложение.

Если $ array + = $ element медленный, вы можете ожидать, что $ array * $ n также будет медленным, но это не так:

PS> Measure-Command -Expression { $a = @($false) * 100000 } | Format-List -Property "Ticks"
Ticks : 20131

Так же, как в Java есть класс StringBuilder, позволяющий избежать создания нескольких объектов при добавлении, так что, похоже, PowerShell имеет ArrayList.

PS> $al = New-Object System.Collections.ArrayList
PS> Measure-Command -Expression { for($i=0; $i -lt 1000; $i++) {$al.Add($false)} } | Format-List -Property "Ticks"
Ticks : 447133
PS> $al = New-Object System.Collections.ArrayList
PS> Measure-Command -Expression { for($i=0; $i -lt 10000; $i++) {$al.Add($false)} } | Format-List -Property "Ticks"
Ticks : 2097498
PS> $al = New-Object System.Collections.ArrayList
PS> Measure-Command -Expression { for($i=0; $i -lt 100000; $i++) {$al.Add($false)} } | Format-List -Property "Ticks"
Ticks : 19866894

Оператор диапазона и Where-Object loop:

PS> Measure-Command -Expression { $a = 1..100000 } | Format-List -Property "Ticks"
Ticks : 239863
Measure-Command -Expression { $a | % {$false} } | Format-List -Property "Ticks"
Ticks : 102298091

Примечания:

  • Я обнуляю переменную между каждым запуском ($a=$null).
  • Тестирование проводилось на планшете с процессором Atom; Вы, вероятно, увидите более высокие скорости на других машинах. [править: примерно вдвое быстрее на настольном компьютере.]
  • Было несколько вариаций, когда я пробовал несколько прогонов. Ищите порядки, а не точные числа.
  • Тестирование проводилось с помощью PowerShell 3.0 в Windows 8.

Подтверждения

Спасибо @ halr9000 за массив * n, @ Скотту Сааду и Ли Десмонду за New-Object и @EBGreen за ArrayList.

Спасибо @ n0rd за то, что заставили меня задуматься о производительности.

12 голосов
/ 23 октября 2008
$array = 1..5 | foreach { $false }
9 голосов
/ 25 января 2016

Вот еще одна идея. Вы должны помнить, что это .NET под:

$arr = [System.Array]::CreateInstance([System.Object], 5)
$arr.GetType()
$arr.Length

$arr = [Object[]]::new(5)
$arr.GetType()
$arr.Length

Результат:

IsPublic IsSerial Name                                     BaseType                                                                                               
-------- -------- ----                                     --------                                                                                               
True     True     Object[]                                 System.Array                                                                                           
5
True     True     Object[]                                 System.Array                                                                                           
5

Использование new() имеет одно явное преимущество: когда вы программируете в ISE и хотите создать объект, ISE подскажет вам все комбинации параметров и их типы. У вас нет этого с New-Object, где вы должны помнить типы и порядок аргументов.

ISE IntelliSense for new object

9 голосов
/ 22 октября 2008
$array = @()
for($i=0; $i -lt 5; $i++)
{
    $array += $i
}
7 голосов
/ 22 октября 2008

Я нашел решение использовать командлет New-Object для инициализации массива правильного размера.

$array = new-object object[] 5 
for($i=0; $i -lt $array.Length;$i++)
{
    $array[$i] = $FALSE
}
5 голосов
/ 22 октября 2008

Если я не знаю размер заранее, я использую arraylist вместо массива.

$al = New-Object System.Collections.ArrayList
for($i=0; $i -lt 5; $i++)
{
    $al.Add($i)
}
0 голосов
/ 24 октября 2018

Или попробуйте эту идею. Работает с powershell 5.0 +.

[bool[]]$tf=((,$False)*5)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...