質問: ### やりたいこと linqで順序を保持したままフィルターしたいです。 例えば、リストの中にソートされていない1から10000まで数字があって、100が入力されたら50から150までを、300が入力されたら250から350までを、ソートされたコレクションとして返したいです。 このとき、まずは1から10000までの入ったリストをorderbyでソートし、その後各入力に対してwhereでフィルタした結果を返したいのですが、whereを使うと順序が保持されないとドキュメントにありました。 このような場合にどういった処理をすればよいか教えてください。 ### 補足 さきにorderbyしたいのは、実際には入力というのがクライアントからの膨大な数になるからです。 それでも毎回フィルタしてからorderbyしたほうがいいでしょうか? ### 補足2 試してみたところ、最初にorderbyをするだけで何秒もかかってしまうので、最初にorderbyするという選択肢はなくなりました。 本当にやりたいことは、{時間、性別}というデータのコレクションがあり(これが1から10000まで)、{5時、男}という入力に対して、まず4時半から5時半の間で最も遅い時間のその性別のデータをとってくるということです。 最初にこのデータのコレクションをソートするというのはやめるとして、どのような方法で取得するのがよいでしょうか? {最遅5時半、最早4時半、男}で絞り込んだ後、orderbydescendingしてfirstordefaultをとってくるのがよいでしょうか? これだとやはり1入力で1orderbyになりますが... そもそも1件しか必要ないのですが、ソートせずにとってきたりする方法もありますか? 時間についてminをつかおうとおもったのですが、データのインスタンスではなく時間がとれてしまいました。 ※順序を保持したまま〜というのはなしになったため、タイトルを変えました。
回答: 試してみましたか?基本的にリストならLinqはソート順序をキープしてくれますよ。 コレクションによって実装が異なったりするので一概には言えないんですけどね。 しかも安定したソートなので同値であっても順序の入れ替えとかも起きません。 Where使った後OrderByする方が自然ではあるんですが、仮に逆順でもよっぽどのことをしなければ大丈夫です。 ``` // 50,51,52,... 148,149,150 foreach (var number in Enumerable.Range(1, 10000) .OrderBy(number => number) .Where(number => 50 <= number && number <= 150)) { Console.WriteLine($"{number}"); } // 150,149,148,... 52,51,50 foreach (var number in Enumerable.Range(1, 10000) .OrderByDescending(number => number) .Where(number => 50 <= number && number <= 150)) { Console.WriteLine($"{number}"); } ``` 補足への回答 ====== 先にコレクションをソートしておけば大丈夫ですよ。 最初に言った通りWhere句で並び順が崩れるなんていうことは**普通ありません**。 必要に応じてWhereメソッドを呼び出すというのを何度もやったらいいです。 もしも現在の実装で並び順が崩れるため、パフォーマンスを考えてWhere句の後にOrderByしない解決方法を必要としている場合はコードを添付してください。 ``` // コレクションを最初に作る時にソートしておく MyCollection = new Collection(DataSource.OrderBy(item => item.Id)); // 入力されたら前後50の範囲を返す関数を作っとく。 public static class MyItemExtension { public static IEnumerable Sampling(this IEnumerable items, int threshold) { return items.Where(item => (threshold - 50) <= item.Id && item.Id <= (threshold + 50)); } } // 50~150受け取りたい処理にパス SomeFunction(MyCollection.Sampling(100)); // 250~350受け取りたい処理にパス SomeFunction(MyCollection.Sampling(300)); ``` パフォーマンス上のことで更に追記 ================ もう一つ。1から10000まで隙間なくデータが埋まっているならWhere句よりGetRangeを使うこともできます。 リストはコピーされるものの、こちらはリスト全体を舐めないですむので場合によっては高速になります。 ``` public static class ListExtension { public static List Sampling(this List items, int threshold) { // startIndexを計算で求められる場合にはそうする。 // 1から10000のリストなので追加で1引いた数にすれば良い。 int begin = threshold - 51; return items.GetRange(begin, 101); // 50 ~ 150は101要素だから。 } } ``` ※コレクションによっては最善の方法でキー探索とかしてくれるかもしれませんが、わかりませんしね。。