Ініціалізація масиву PowerShell


78

Який найкращий спосіб ініціалізувати масив у 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

1
розкажіть нам, що ви намагаєтесь досягти, і, можливо, ми зможемо надати вам кращу відповідь на "ідіоматичну PowerShell". Мені ніколи не потрібно було створювати масив у PowerShell.
Пітер Сіл

Я не бачив, щоб хтось конкретно згадував, що масиви незмінні. Після створення їх неможливо змінити.
AdminOfThings

Відповіді:


47

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

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

Це працює, якщо $ arr ще не визначено.

ПРИМІТКА. Є кращі (і більш ефективні) способи зробити це ... див. Https://stackoverflow.com/a/234060/4570 нижче як приклад.


13
Це дуже повільно. Оскільки масиви .NET не можуть бути змінені, це по суті виділяє новий масив з додатковим простором для нового елемента та копіює дані, тому, якщо у фрагменті коду вище ви перейдете $i -lt 5до, $i -lt 500000вам доведеться чекати довгий час, поки він не закінчиться.
n0rd

Я роблю цей метод постійно, під час обстрілів. baaad звичка при написанні сценаріїв, які повинні дотримуватися. це абсолютно непотрібний час O (n ^ 2). навіть під час обстрілу мені доводиться зупиняти це і робити це правильно, іноді. це надзвичайно зручно.
Nacht

Як правило, я взагалі не використовую цей метод, якщо чесно, я зазвичай використовую ArrayListзамість цього щось подібне. Тоді це технічно не масив.
Девід Мохундро

6
Це не належний спосіб зробити це. Це просто спрацьовує через те, як PowerShell обробляє масиви. Використовуйте це:$arr = New-Object bool[] 5
marsze

94

Ось ще два способи, обидва дуже стислі.

$arr1 = @(0) * 20
$arr2 = ,0 * 20

Дуже приємно, я намагався це зрозуміти сьогодні вранці, і, думаю, ви дали найбільш стислий спосіб ініціалізації масиву.
Кріс Саттон,

Дякую. Тут є більше інформації в моєму блозі, де ця тема з’явилася у вересні 2007 року. Halr9000.com/article/430
halr9000

Напевно, я товстий. Хтось може пояснити, що це робить і для чого призначений * 20? 20 не відображається ні в чужій відповіді, ні в запитанні.
Luke Puplett

схоже, що масиви PowerShell приймають оператор множення, який просто робить свої копії багато разів. дуже здорово.
Nacht

@ halr9000 ваше посилання не працює. Ось чому ми вважаємо корисним розміщувати відповідну інформацію всередині самої відповіді. :)
Куллуб

54

Ви також можете покластися на значення конструктора за замовчуванням, якщо хочете створити набраний масив:

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

Значення за замовчуванням bool , мабуть, хибне, тому це працює у вашому випадку. Аналогічним чином, якщо ви створюєте набраний масив int [] , ви отримаєте значення за замовчуванням 0.

Ще один класний спосіб, який я використовую для ініціалізації масивів, полягає в наступному скороченні:

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

Або якщо ви хочете ініціалізувати діапазон, я іноді вважаю це корисним:

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

Сподіваюся, це було дещо корисно!


1
Або:$a = @(); $a += ...
марше

1
Більш новий (PS 5.0) альтернатива New-Object , що я знаходжу більш зручним для читання (Intellisense , доступні в ISE): 1-розмірність масиву: $array = [int[]]::new(5). Двовимірний масив:$array = [int[][]]::new(5,3)
Петру Захарія

40

Оригінальний приклад повертає помилку, оскільки масив створюється порожнім, тоді ви намагаєтесь отримати доступ до n-го елемента, щоб присвоїти йому значення.

Тут є ряд творчих відповідей, багато з яких я не знав до прочитання цього допису. Все добре для невеликого масиву, але, як зазначає n0rd, є суттєві відмінності в продуктивності.

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

Резюме

  • New-Objectі @(somevalue)*nшвидкі (близько 20 тис. галочок на 100 тис. елементів).
  • Створення масиву за допомогою оператора діапазону n..mвідбувається в 10 разів повільніше (200 тис. Галочок).
  • Використання ArrayList із Add()методом у 1000 разів повільніше базової лінії (20 мільйонів галочок), як і циклічне проходження масиву, що вже має розмір, за допомогою for()або ForEach-Object(aka foreach, %).
  • Додавання символу +=- найгірше (2 мільйони галочок на 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цикл:

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, @Scott Saad та Lee Desmond для New-Object та @EBGreen за ArrayList.

Дякую @ n0rd за те, що я змусив задуматися про продуктивність.


Приголомшлива поломка. У мене був гарний скрипт, який використовував $ _. Split ('') [0..1] для перевірки дати та часу в журналі IIS, але змінився на підхід $ _. Indexof, коли час обробки зростав експоненціально, переглядаючи 100000 журналів записів.
Кірт Карсон,


11

Ось ще одна ідея. Ви повинні пам’ятати, що це .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 для нового об’єкта


1
::new()справді зручний; Варто зазначити, що для цього потрібен PSv5 +.
mklement0


7

Рішенням, яке я знайшов, було використання командлета New-Object для ініціалізації масиву належного розміру.

$array = new-object object[] 5 
for($i=0; $i -lt $array.Length;$i++)
{
    $array[$i] = $FALSE
}

5

Якщо я не знаю розмір спереду, я використовую список масивів замість масиву.

$al = New-Object System.Collections.ArrayList
for($i=0; $i -lt 5; $i++)
{
    $al.Add($i)
}

Навіщо використовувати це замість масиву та + =?
Райан Фішер

Особливої ​​причини немає. Просто звички з більш обмежувальних мов, які вимагають попереднього вимірювання розмірів масивів. Плюс мені подобаються додаткові функції, вбудовані в списки композицій.
EBGreen

Крім того, якщо ви помітили, я також надав масив + = відповідь. Це все було зроблено більше 3 років тому, перш ніж було визначено, як слід працювати СО. Сьогодні я б поклав обидва методи в одну відповідь.
EBGreen

3
Неможливо змінити розмір масивів @RyanFisher, тому використання + = робить повну копію всього масиву кожного разу, коли ви телефонуєте + =. Це означає, що + = - це O (n), тоді як ArrayList.Add () - O (1). На моєму досвіді, якщо ви щось віддалено виконуєте з масивами, вам буде краще використовувати ArrayList.
Bacon Bits

0

Або спробуйте цю ідею. Працює з PowerShell 5.0+.

[bool[]]$tf=((,$False)*5)

Вимірювання цієї команди із 100000 елементами замість 5 призвело до 526247 галочок. В @(false) * 100000результаті вимірювань було отримано 91802 кліща.
AdminOfThings

0

Ось ще один типовий спосіб:

$array = for($i = 0; $i -le 4; $i++) { $false }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.