PowerShell の見えないアキュムレータ [PowerShell]
ある数値の配列が与えられたときにそこから重複要素を省いた配列を得る関数を PowerShell で書くとこうなる。(どう書く?orgに投稿したもの [1] を少し改変した)
function uniq ($xs) {
$seen = @{}
foreach ($x in $xs) {
if (-not $seen[$x]) { $x }
$seen[$x] = $true
}
}
ここで既出の要素を判定しているのはともかくとして結果を格納する配列はどうした?と思うかもしれない。でもこれでいいのだ。
4行目の if の { $x } だけからなる節は変数 $x の内容を評価する。PowerShell では値がある文の値は何も言わなければ出力されるので以下のようになる。
PS D:\ps1> uniq 3,1,4,1,5,9,2,6,5,3,5,8,9,7,9 3 1 4 5 9 2 6 8 7
じゃあこれは要素をユニーク化した配列内容を表示する関数であって配列を返すものではないじゃないか。それも違う。以下の例を見てほしい。
PS D:\ps1> $xs = uniq 3,1,4,1,5,9,2,6,5,3,5,8,9,7,9 PS D:\ps1> $xs.length 9 PS D:\ps1> $xs.gettype() IsPublic IsSerial Name BaseType -------- -------- ---- -------- True True Object[] System.Array
$xs にはちゃんと要素がユニークになった配列が入っている。 PowerShell では関数やスクリプトの出力はパイプラインの中にあればオブジェクトのストリームと解釈されるが、変数への代入のような文脈では自動的にアキュムレータを作ってそこに出力を掻き集めてくれるのだ。
カレントディレクトリ以下のファイル・ディレクトリを再帰的に集めて配列に入れたければ以下のように書く。(実際には Get-ChildItem コマンドレットに -recurse オプションが使えるので自分で作る必要はない)
function walk-dir ($path) {
dir $path | foreach {
if ($_ -is [System.IO.DirectoryInfo]) { walk-dir $path/$_ }
$_
}
}
$fs = walk-dir .
上記の4行目のような文はいわば「無標の yield」(勝手に名前を付けた)であって「 yield $_ 」のようなものだと思うとよいのかもしれない。Python などの yield との違いは常にコレクション全体を生成するのであってジェネレータのように計算が途中で中断された状態ではないという点だ(つまり無限リストは出来ない)。
[1] http://ja.doukaku.org/comment/1664/
コメント 0