F# で LINQ 入門 Count LongCount
シーケンスの要素数を返します。IEnumerable(Of T) を受け取ったとき、.NET Framework 2.0 の頃では、いちいち List(Of T) に入れて要素数を取得する……なんてアプローチを逐一とる必要がありましたが、このメソッドのおかげで簡単に要素数を調べることが出来ます。LongCount は要素数が整数値を超える場合に使用できます。膨大な数のログを処理するときなどには有効なのではないでしょうか。
まずはシーケンスを作成します。
> open System open System.Linq let target = seq{ 1..10 }
要素数を取得します。
> target.Count();; val it : int = 10
期待通り。では、整数値の最大を超える要素数の取得はどうだろう。
> let target' = seq{ 1..System.Int32.MaxValue + 1 };; val target' : seq<int> > target'.Count();; val it : int = 0
なぜか 0 になってしまいました。System.Int32.MaxValue + 1 は何が評価されるんだろう。
> System.Int32.MaxValue + 1;; val it : int = -2147483648
マイナス……だと……? 実は F# では、最大値を超えるような計算をすると、値が1周して符号が反転してしまう振る舞いがデフォルトとなっています。減算でも同様。
> System.Int32.MinValue - 1;; val it : int = 2147483647
C# や VB のように、その型が扱うことのできる値の範囲を超える場合はエラー、としたい場合は Checked モジュールを open します。
> open Checked;; > System.Int32.MaxValue + 1;; > System.OverflowException: 算術演算の結果オーバーフローが発生しました。 場所 <StartupCode$FSI_0017>.$FSI_0017.main@() エラーのため停止しました
とはいえ、どちらにしても int では System.Int32.MaxValue + 1 の値を表現できないため、int64 を使用します。
> let target' = seq{ 1L..(int64 System.Int32.MaxValue + 1L) };; val target' : seq<int64> > target'.Count();; System.OverflowException: 算術演算の結果オーバーフローが発生しました。 場所 System.Linq.Enumerable.Count[TSource](IEnumerable`1 source) 場所 <StartupCode$FSI_0024>.$FSI_0024.main@() エラーのため停止しました
Count メソッドはシーケンスの要素数を整数値で返すメソッドなので、シーケンスの要素数が整数値を超える場合の呼び出しではエラーになってしまいます。ここでようやく LongCount の出番がやってきます。
> target'.LongCount();; val it : int64 = 2147483648L
LongCount でも扱えないようなシーケンスを扱っているのだとしたら、システムを見直した方がいいんじゃないかと思います。BigCount を自分で実装する?