オブジェクトの数を数える時によく使われるものといえば?
Measure-Objectでしょうか? Measure-Objectでしょうか?
今回はさらっとちょっとだけ見てみましょう。
個数のカウント
よく使うのはどれでしょうか?
.Lengthプロパティ?.Countプロパティ?- `Measure-Object`` コマンドレット?
System.Linq.EnumerableのSystem.Linq.Enumerableメソッド?
.Lengthプロパティは .NET通りなのでいいでしょう。
ここではPowerShellで奇妙に感じる.CountプロパティとCmdletである.Countを見てみます。
.Countプロパティって気持ち悪いけど便利
PowerShellのオブジェクトは、そこかしこで .Countプロパティを使って個数を数えることができます。*1

そう、たとえ配列でなくてもです。
気になるのが文字列System.Stringに対してですが、「インテリセンス上はLengthしかでず」これは文字長を取得します。*2

しかし、インテリセンスになくとも.Countが使える不思議。これは.Countであれば、必ず1になります。空文字.Countであってもです。
まぁ、便利は便利です。ICollectionインタフェースの.Countプロパティ同様に取得コストがほぼないので。
しかし、配列でもはいのに要素数が.Countで取れるのは気持ち悪いことこの上ない。
.Countプロパティの利用は罠がある
PowerShell 6.1からPSCustomObjectのCountプロパティが機能するようになっています。
以下の内容はPowerShell 6.0以下のバージョンにおいて該当します。
大体のオブジェクトに対して.Countで個数が取れるのですが、数少ない例外が.Countです。
この型に対しては、.Countプロパティでの個数取得できません。

となった時に、.Lengthプロパティが使えるかというと同様にnullが返ってきます。
これを回避するには、いったん配列@()で包んでしまいます。
Measure-Object を使って個数を数える
ここでようやく、Measure-Object Cmdletが出てきます。
プロパティを使って取得できるなら、可能な限りそっちを使うのがふつーなのですが、取れないなら数えるしかないと。
よくあるパターンでPSCustomObject型を生成して個数を数えるとこうでしょうか?
Measure-Object の罠
正確には、パイプライン|の罠です。
PowerShellのパイプラインは、基本的にはオブジェクトを1つずつ渡します。*3
ということは? パイプラインに1000000オブジェクト渡すと、1000000オブジェクト渡しきるのを待たないといけないということです。これはひどい。
そのため、以下のように書いた日には18秒ぐらいぽけ~(())と待つことになります。やめましょう。
(1000000..1 | measure).Count
個数カウントの方法とベンチマーク
楽をしたいので、C# で良くやるようにPowerShellにも持ち込みます。
PowerShellの長所でもあり短所であるパイプラインです。コストがパイプラインにかかっているので、避ければいいのです。
ということで、System.Linq.Enumerableを使えばいいでしょう。それぞれのベンチをとってみます。
| 方法 | 1回目 | 2 回目 | 3回目 | 平均(ms) |
|---|---|---|---|---|
| .Count | 261.2029 | 155.8287 | 145.4331 | 187.488 |
| Measure-Object | 19268.5511 | 19558.6135 | 19934.1167 | 19587.093 |
| Linq.Enumerable.Count() | 405.0513 | 357.8473 | 389.6365 | 384.18 |
当然の結果ですが、想定通りですね。
Min / Max の取得
この件がもっと影響を及ぼすのが、Min / Maxです。
Measure-Objectはとても便利なCmdletで、Min/Max/Average/Countをプロパティを指定して計測できます。
しかし、パイプライン越しの利用が主体になるため、オブジェクト個数に応じて時間がかかります。(1-1000程度ならmsで完了するのでいいのですが)
そういう意味では、System.Linq.Enumerableは、PowerShellからも使いやすいので是非使えばいいでしょう。
Min
| 方法 | 1回目 | 2 回目 | 3回目 | 平均(ms) |
|---|---|---|---|---|
| Measure-Object | 22628.2811 | 22705.8116 | 23010.3391 | 22781.477 |
| Linq.Enumerable.Min() | 451.5155 | 431.6266 | 566.3141 | 483.152 |
Max
| 方法 | 1回目 | 2 回目 | 3回目 | 平均(ms) |
|---|---|---|---|---|
| Measure-Object | 23102.14 | 22624.6367 | 22535.7175 | 22754.164 |
| Linq.Enumerable.Max() | 460.4299 | 548.7585 | 390.1181 | 466.435 |
Average
| 方法 | 1回目 | 2 回目 | 3回目 | 平均(ms) |
|---|---|---|---|---|
| Measure-Object | 20982.5092 | 21166.3688 | 20576.0699 | 20908.315 |
| Linq.Enumerable.Agerage() | 382.4254 | 370.6433 | 360.0512 | 371.03 |
まとめ
.Countプロパティが使える時は素直に使いましょう。
Measure-Objectは量が十分抑えられる時には便利ですね。
Linq.Enumerableくぁわいい。