PowerShell-Array-Initialisierung

77

Wie kann ein Array in PowerShell am besten initialisiert werden?

Zum Beispiel der Code

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

erzeugt den Fehler

Array assignment failed because index '0' was out of range.
At H:\Software\PowerShell\TestArray.ps1:4 char:10
+         $array[$ <<<< i] = $FALSE
Eric Ness
quelle
1
Sagen Sie uns, was Sie erreichen möchten, und vielleicht können wir Ihnen eine bessere "idiomatische PowerShell" -Antwort geben. Ich musste noch nie ein Array in PowerShell neu erstellen.
Peter Seale
Ich habe niemanden speziell erwähnen sehen, dass Arrays unveränderlich sind. Einmal erstellt, können sie nicht mehr geändert werden.
AdminOfThings

Antworten:

47

Noch eine Alternative:

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

Dieser funktioniert, wenn $ arr noch nicht definiert ist.

HINWEIS - Es gibt bessere (und leistungsfähigere) Möglichkeiten, dies zu tun. Ein Beispiel finden Sie unter https://stackoverflow.com/a/234060/4570 .

David Mohundro
quelle
13
Das ist super langsam. Da die Größe von .NET-Arrays nicht geändert werden kann, wird im Wesentlichen ein neues Array mit zusätzlichem Speicherplatz für neue Elemente zugewiesen und Daten kopiert. Wenn Sie also im obigen Code-Snippet zu wechseln $i -lt 5, müssen $i -lt 500000Sie lange warten, bis es fertig ist.
n0rd
Ich mache diese Methode die ganze Zeit beim Schälen. Baaad Gewohnheit für das Schreiben von Skripten, die bleiben müssen. Es ist eine völlig unnötige O (n ^ 2) Zeit. Selbst wenn ich schieße, muss ich damit aufhören und es manchmal richtig machen. es ist aber super praktisch.
Nacht
Ich benutze diese Methode normalerweise überhaupt nicht, um ehrlich zu sein, ich benutze normalerweise eine ArrayListoder etwas Ähnliches. Es ist dann aber technisch gesehen kein Array.
David Mohundro
6
Dies ist nicht der richtige Weg, dies zu tun. Es funktioniert einfach aufgrund der Art und Weise, wie PowerShell Arrays verarbeitet. Verwenden Sie diese:$arr = New-Object bool[] 5
Marsze
94

Hier sind zwei weitere Möglichkeiten, beide sehr prägnant.

$arr1 = @(0) * 20
$arr2 = ,0 * 20
halr9000
quelle
Sehr schön, ich habe heute Morgen versucht, dies herauszufinden, und ich denke, Sie haben die prägnanteste Möglichkeit gegeben, ein Array zu initialisieren.
Chris Sutton
Vielen Dank. Es gibt mehr Informationen hier auf meinem Blog, wo dieses Thema im September 2007 auftauchte
halr9000
Ich muss dick sein. Kann jemand erklären, was dies tut und wofür die * 20 ist? 20 erscheint nicht in der Antwort eines anderen oder in der Frage.
Luke Puplett
Es sieht so aus, als ob Powershell-Arrays den Multiplikationsoperator verwenden, der einfach so oft Kopien von sich selbst erstellt. ziemlich cool.
Nacht
@ halr9000 dein link ist kaputt. Aus diesem Grund finden wir es nützlich, die relevanten Informationen in die Antwort selbst aufzunehmen. :)
Cullub
54

Sie können sich auch auf den Standardwert des Konstruktors verlassen, wenn Sie ein typisiertes Array erstellen möchten:

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

Der Standardwert eines Bools ist anscheinend falsch, sodass dies in Ihrem Fall funktioniert. Wenn Sie ein typisiertes int [] -Array erstellen , erhalten Sie ebenfalls den Standardwert 0.

Eine andere coole Art, Arrays zu initialisieren, ist die folgende Kurzform:

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

Oder wenn Sie einen Bereich initialisieren möchten, habe ich dies manchmal nützlich gefunden:

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

Hoffe das war etwas hilfreich!

Scott Saad
quelle
1
Oder:$a = @(); $a += ...
Marsze
1
Eine neuere (PS 5.0) Alternative zu New-Object, die ich besser lesbar finde (Intellisense in ISE verfügbar): 1-dimensionales Array : $array = [int[]]::new(5). 2-dimensionale Anordnung:$array = [int[][]]::new(5,3)
Petru Zaharia
39

Das ursprüngliche Beispiel gibt einen Fehler zurück, da das Array leer erstellt wurde. Anschließend versuchen Sie, auf das n-te Element zuzugreifen, um ihm einen Wert zuzuweisen.

Hier gibt es eine Reihe kreativer Antworten, viele, die ich vor dem Lesen dieses Beitrags nicht kannte. Alle sind für ein kleines Array in Ordnung, aber wie n0rd hervorhebt, gibt es signifikante Leistungsunterschiede.

Hier benutze ich Measure-Command, um herauszufinden, wie lange jede Initialisierung dauert. Wie Sie vielleicht erraten haben, ist jeder Ansatz, der eine explizite PowerShell-Schleife verwendet, langsamer als der, der .NET-Konstruktoren oder PowerShell-Operatoren verwendet (die in IL oder nativem Code kompiliert würden).

Zusammenfassung

  • New-Objectund @(somevalue)*nsind schnell (ca. 20.000 Ticks für 100.000 Elemente).
  • Das Erstellen eines Arrays mit dem Bereichsoperator n..mist 10x langsamer (200.000 Ticks).
  • Die Verwendung einer ArrayList mit der Add()Methode ist 1000-mal langsamer als die Basislinie (20 Millionen Ticks), ebenso wie das Durchlaufen eines bereits großen Arrays mit for()oder ForEach-Object(auch bekannt foreachals %).
  • Das Anhängen mit +=ist das Schlimmste (2 Millionen Ticks für nur 1000 Elemente).

Insgesamt würde ich sagen, Array * n ist "am besten", weil:

  • Es ist schnell.
  • Sie können einen beliebigen Wert verwenden, nicht nur den Standardwert für den Typ.
  • Sie können sich wiederholende Werte erstellen (geben Sie dies zur Veranschaulichung an der Powershell-Eingabeaufforderung ein: (1..10)*10 -join " "oder ('one',2,3)*3)
  • Knappe Syntax.

Einziger Nachteil:

  • Nicht offensichtlich. Wenn Sie dieses Konstrukt noch nicht gesehen haben, ist nicht ersichtlich, was es bewirkt.

Beachten Sie jedoch, dass in vielen Fällen, in denen Sie die Array-Elemente auf einen bestimmten Wert initialisieren möchten, ein stark typisiertes Array genau das ist, was Sie benötigen. Wenn Sie alles auf initialisieren $false, wird das Array dann jemals etwas anderes als $falseoder enthalten $true? Wenn nicht, dann New-Object type[] nist der "beste" Ansatz.

Testen

Erstellen und skalieren Sie ein Standardarray und weisen Sie dann Werte zu:

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

Das Erstellen eines Booleschen Arrays ist etwas langsamer als ein Array von Object:

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

Es ist nicht offensichtlich, was dies bewirkt. In der Dokumentation zu New-Object heißt es lediglich, dass der zweite Parameter eine Argumentliste ist, die an den .Net-Objektkonstruktor übergeben wird. Bei Arrays ist der Parameter offensichtlich die gewünschte Größe.

Anhängen mit + =

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

Ich hatte es satt, darauf zu warten, also Strg + C dann:

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

So wie (6 * 3) konzeptionell (6 + 6 + 6) ähnlich ist, sollte ($ somearray * 3) das gleiche Ergebnis liefern wie ($ somearray + $ somearray + $ somearray). Bei Arrays ist + jedoch eher eine Verkettung als eine Addition.

Wenn $ array + = $ element langsam ist, können Sie erwarten, dass $ array * $ n auch langsam ist, aber es ist nicht:

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

So wie Java über eine StringBuilder-Klasse verfügt, um zu vermeiden, dass beim Anhängen mehrere Objekte erstellt werden, scheint PowerShell über eine ArrayList zu verfügen.

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

Bereichsoperator und Where-ObjectSchleife:

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

Anmerkungen:

  • Ich habe die Variable zwischen jedem Lauf ( $a=$null) auf Null gesetzt .
  • Das Testen erfolgte auf einem Tablet mit Atom-Prozessor. Sie würden wahrscheinlich schnellere Geschwindigkeiten auf anderen Maschinen sehen. [Bearbeiten: Auf einem Desktop-Computer ungefähr doppelt so schnell.]
  • Es gab einiges an Abwechslung, als ich mehrere Läufe ausprobierte. Suchen Sie nach Größenordnungen und nicht nach exakten Zahlen.
  • Das Testen erfolgte mit PowerShell 3.0 unter Windows 8.

Danksagung

Vielen Dank an @ halr9000 für Array * n, @ Scott Saad und Lee Desmond für New-Object und @EBGreen für ArrayList.

Vielen Dank an @ n0rd, dass ich über Leistung nachgedacht habe.

Sellerie Mann
quelle
Genialer Zusammenbruch. Ich hatte ein nettes Skript, das $ _. Split ('') [0..1] verwendete, um Datum und Uhrzeit im IIS-Protokoll zu überprüfen, wechselte jedoch zu einem $ _. Indexof-Ansatz, als die Verarbeitungszeit exponentiell durch 100.000 Protokolle ging Einträge.
Kirt Carson
12
$array = 1..5 | foreach { $false }
Peter Seale
quelle
1
Ich mag das, ich habe ein% anstelle des foreach fallen lassen und es gibt ihm eine wirklich enge Initialisierung.
Chris Sutton
11

Hier ist eine andere Idee. Sie müssen sich daran erinnern, dass es sich um .NET handelt:

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

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

Ergebnis:

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

Die Verwendung new()hat einen entscheidenden Vorteil: Wenn Sie in ISE programmieren und ein Objekt erstellen möchten, gibt Ihnen ISE einen Hinweis auf alle Paramer-Kombinationen und deren Typen. Sie haben das nicht mit New-Object, wo Sie sich die Arten und die Reihenfolge der Argumente merken müssen.

ISE IntelliSense für neues Objekt

AdamL
quelle
1
::new()ist in der Tat praktisch; Erwähnenswert ist, dass PSv5 + erforderlich ist.
mklement0
10
$array = @()
for($i=0; $i -lt 5; $i++)
{
    $array += $i
}
EBGreen
quelle
7

Die Lösung, die ich gefunden habe, bestand darin, das Cmdlet New-Object zu verwenden, um ein Array mit der richtigen Größe zu initialisieren.

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

Wenn ich die Größe im Voraus nicht kenne, verwende ich eine Arrayliste anstelle eines Arrays.

$al = New-Object System.Collections.ArrayList
for($i=0; $i -lt 5; $i++)
{
    $al.Add($i)
}
EBGreen
quelle
Warum dies anstelle eines Arrays und + = verwenden?
Ryan Fisher
Kein bestimmter Grund. Nur Gewohnheiten aus restriktiveren Sprachen, die eine Vordimensionierung der Arraygrößen erfordern. Außerdem mag ich die zusätzlichen Funktionen, die in Arraylisten integriert sind.
EBGreen
Auch wenn Sie bemerken, habe ich auch ein Array + = Antwort bereitgestellt. Dies alles wurde vor über 3 Jahren gemacht, bevor die Art und Weise, wie SO funktionieren sollte, wirklich definiert wurde. Heute würde ich beide Methoden in einer Antwort zusammenfassen.
EBGreen
3
@ RyanFisher-Arrays können nicht in der Größe geändert werden. Wenn Sie also + = verwenden, wird bei jedem Aufruf von + = eine vollständige Kopie des gesamten Arrays erstellt. Das heißt, + = ist O (n), während ArrayList.Add () O (1) ist. Nach meiner Erfahrung ist es besser, eine ArrayList zu verwenden, wenn Sie mit Arrays aus der Ferne herumspielen.
Bacon Bits
0

Oder versuchen Sie dies eine Idee. Funktioniert mit Powershell 5.0+.

[bool[]]$tf=((,$False)*5)
Michael Koza
quelle
Das Messen dieses Befehls mit 100000 Elementen anstelle von 5 ergab 526247 Ticks. Die Messung @(false) * 100000ergab 91802 Zecken.
AdminOfThings
0

Hier ist ein anderer typischer Weg:

$array = for($i = 0; $i -le 4; $i++) { $false }
js2010
quelle