PowerShell

Array要素の削除に$null代入するとビックリすることがある

概要

PowerShell の配列の要素を削除する際、「要素に $null を書き込む」と解説してあるサイトをよく見かける。ある意味において間違いではないが、「配列要素の切り詰め=対象に $null 代入」と記憶していると処理後の配列について直感と異なる結果が返るので記録しておく。

環境

バージョン情報
PS> $PSVersionTable

Name                           Value
----                           -----
PSVersion                      5.1.15063.502
PSEdition                      Desktop
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
BuildVersion                   10.0.15063.502
CLRVersion                     4.0.30319.42000
WSManStackVersion              3.0
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1

実験

適当な配列を与え、その 0 番目の要素に $null を書いてみる。

RemovingArrayElementTest.ps1
PS> $array = (1..10)
PS> foreach ($i in $array) { $a += $i }
PS> $a
55

PS> $array.Length
10

PS> $array[0] = $null
PS> $a = 0
PS> foreach ($i in $array) { $a += $i }
54

一見すると 0 番目の要素であった 1 が「なくなった」ように見える。
しかし、

RemovingArrayElementTest(cont'd).ps1
PS> $array.Length
10 # 「 9 じゃないの!?」

PS> $array[1]
2 # 「 3 じゃないの!?」

「要素を削除した」にもかかわらず、次のようになっていることがわかる。

  • 配列の長さが変わらない
  • 配列の要素が詰まって(シフトして)いない

何が起きているのか

その答えはこちら1 に書いてあった。

+=演算子 で文字列の追加を行う場合に、PowerShell の @() による Array は固定長なので配列をいちいち作り直します。

PowerShell の配列は固定長である。したがって配列の長さを変えられないのが仕様であった。

ではさっきのテストコードにおいて、 $array[0] はどうなっているのだろう。

RemovingArrayElementTest(cont'd2).ps1
PS> $array[0] -eq $null
True

当たり前だが、自ら行った操作通り $null であった。したがって $array$null を先頭に残りが 2 から 10 の整数が格納された、長さ 10 の配列であることがわかる。

当初のテストコードのように、 $null が要素に含まれる配列をイテレータとして利用する分には問題ない。一方で、 Length や Count で配列の長さを取得し、条件分岐等に使うケースでは問題になり得る。

$null 要素を取り除く

フィルターとしての -ne を使って、次のように実現できる。

RenewArray.ps1
PS> $newarray = $array -ne $null
PS> $newarray.Length
9

結論

  • @() で定義される Array は固定長である
  • 配列要素に $null を書き込むと:
    • イテレータとしては直感通り動作する
    • 長さが問題になるケースでは注意が必要

補足

参考文献1 にあるとおり、 Array の要素を増減させるには配列を作り直して再度代入する必要があるため、計算コストが高い。配列の要素が頻繁に増減する環境では、代わりに(固定長ではない) ArrayList や List を使う方が計算コストを低くできる。

参考文献