質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Q&A

解決済

5回答

24091閲覧

ラムダ式を使った複数キーでの並び替え

yoshin

総合スコア39

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

0グッド

2クリップ

投稿2016/05/30 10:59

###前提・実現したいこと
ラムダ式を使って複数のソートキーを組み合わせてListを昇順に並び替えたいです。
このとき、ソートキーはユーザーの設定により変動するものとします。

###発生している問題・エラーメッセージ
たとえば、ソートキーを3つ設定(第1ソートキー、第2ソートキー、第3ソートキー)できるとした場合、
3*2 = 6つの並び替えパターンがあると思います。

下記のソースコードのように、
力技で6つのパターンを用意する方法は思いつくのですが、
あまり良いやり方とはおもいません。
(ソートキーの数が増えるとパターンがどんどん増えてしまうので。)

第1ソートキー、第2ソートキー、第3ソートキーを引数として受け取って、
柔軟に並び替えを行うよい方法はないでしょうか?

###該当のソースコード

※orderbycaseにはユーザーが設定したソートキーから取得したケース番号が入ると想定します。

public void orderby(int orderbycase, List<User> beforeList)
{

var afterlist = new List<User>(); switch (orderbycase) { case 1: afterlist = beforeList.OrderBy(a => a.name) .ThenBy(a => a.address) .ThenBy(a => a.birth).ToList(); break; case 2: afterlist = beforeList.OrderBy(a => a.name) .ThenBy(a => a.birth) .ThenBy(a => a.address).ToList(); break; ※case 3 ~ 6についてもOrderBy,ThenByメソッドを作成します。 } } public class User { public string name { get; set; } public string address { get; set; } public string birth { get; set; } }

###補足情報(言語/FW/ツール等のバージョンなど)
.NET Framework4.6.1
C#

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答5

0

lang

1static IOrderedEnumerable<TSource> MyOrderBy<TSource>(this IEnumerable<TSource> list, params Func<TSource, string>[] orders) 2{ 3 if (orders.Length == 0) 4 throw new InvalidOperationException(); 5 6 IOrderedEnumerable<TSource> result = null; 7 foreach (var item in orders) 8 { 9 if (result == null) 10 result = list.OrderBy(source => item(source)); 11 else 12 result = result.ThenBy(source => item(source)); 13 } 14 15 return result; 16}

上記のような拡張メソッドを定義し、

lang

1var users = new List<User>(); 2 3var result = users.MyOrderBy( 4 user => user.address, 5 user => user.birth, 6 user => user.name);

などとするのはいかがでしょうか?

拡張メソッドの引数の型Func<TSource, string>については、
ソートを行うプロパティの型にstring以外も含まれる場合objectにすればいけるかと。
これによりボックス化が発生しますが、IComparableが実装されている型であればラムダ式で渡せます。
IComparableが実装されていない型もソートに使用する場合は、
OrderBy(),ThenBy()IComparerも渡さなければならないため、別の工夫も必要かと思います。

投稿2016/05/31 17:16

TAKA_0921

総合スコア234

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

yoshin

2016/06/01 01:54

回答いただきありがとうございます。 今回は別の方に提示していただいた方法で実装します。 教えていただいた方法も今後役に立ちそうなので勉強してみます。
Zuishin

2016/06/07 04:57

これまでの回答の中ではこの方法が一番高速です。 他の方のように複数回ソートを行うやり方ですと、要素数や条件が増えたら実用になりません。
guest

0

ベストアンサー

こんにちは.

1.ソート手順を配列で定義し,引数に渡せるようにする
2.渡されたソート手順配列をループで回し,得られた要素順にソートを行う
(はじめにafterlistに全要素コピーし,OrderBy関数のみ使用してafterlistを上書きしていく形でソートを重ねる)

ソート手順定義配列の要素の形式は,何も気にしないならば変数名の文字列で良いでしょうし,タイプミスの予防とパフォーマンスを意識するならば,enumにすると良いと思います.

そのうえで,毎回ソート手順の配列を書くのは面倒なので,手順の配列を必要な分だけプリセットとしてstaticな変数で置いておいたり,動的に生成するstatic関数を用意しておいたりして使いまわすといかがでしょうか.

具体的なコードを提示できる時間がないのでヒントのみですみませんが,参考になると幸いです.

※ OrderByのみ使用するにあたって,要素順とは逆順にソートするような対応が必要と推測されます.
※ 当方OrderByを使用した事が無いため,OrderByのみでは達成不可であった場合は申し訳ありません.無視して下さい.

投稿2016/05/30 11:33

編集2016/05/30 11:39
shironegi

総合スコア119

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

yoshin

2016/05/31 04:13

回答いただきありがとうございます。 教えていただいた方法ですと、 やはり並び替えるパターン分ソート手順が必要になるのでしょうか? 私の理解不足でしたらもうしわけありません。
shironegi

2016/05/31 04:43 編集

ソートを行う行は,変数の数分だけで済みます. たとえばソート手順配列を文字列で作るとして,この配列に "name" "birth" "address" の順で要素が入っていたら, address で OrderBy, birth で OrderBy, name で OrderBy, という順でソートすると, OrderBy(a => a.name).ThenBy(a => a.birth).ThenBy(a => a.address) を行ったのと同じ結果のリストが得られるのではないかと考えた次第です. 手順配列を逆順のループで回しながら,順に要素を見て if (要素が "name"だったら) { name で OrderBy してリスト上書き} else if (要素が "address"だったら) { address で OrderBy してリスト上書き} else if (要素が "birth"だったら) { birth で OrderBy してリスト上書き} というバラでソートする仕組みにしておけば, 新しいソートキーを増やしても, else if (要素が ”新しいソートー名"だったら){新しいソートキーで OrderBy してリスト上書き} の部分を追加するだけで済むのではないかと.
shironegi

2016/05/31 10:54 編集

上記の事をコードで表すと,このような関数になります. public List<User> orderby(List<string> orderRequest, List<User> srcList) {  orderRequest.Reverse();  foreach (string request in orderRequest)  {   if (request == "name")   {    srcList = srcList.OrderBy(a => a.name).ToList();   }   else if (request == "adress")   {    srcList = srcList.OrderBy(a => a.address).ToList();   }   else if (request == "birth")   {    srcList = srcList.OrderBy(a => a.birth).ToList();   }  }  return srcList; } こうしておくと,キー1つでのソートからキー3つでのソートまで,orderRequestの内容で全て対応できます.
guest

0

こんな感じでしょうか?

using System; using System.Collections.Generic; using System.Linq; namespace ConsoleApplication1 { static class Program { class Person { public string Name { get; set; } public string Address { get; set; } public DateTimeOffset BirthDay { get; set; } } static Person[] People = new[] { new Person { Name = "Andrew", Address = "Illinois", BirthDay = DateTimeOffset.Parse("1989-11-02") }, new Person { Name = "George", Address = "Kentucky", BirthDay = DateTimeOffset.Parse("1970-02-11") }, new Person { Name = "Denith", Address = "Cicago", BirthDay = DateTimeOffset.Parse("1984-09-16") }, }; static void Main(string[] args) { People.OrderBy(p => p.Name, p => p.Address, p => p.BirthDay).Dump(); People.OrderBy(p => p.Address, p => p.BirthDay, p => p.Name).Dump(); People.OrderBy(p => p.BirthDay, p => p.Name, p => p.Address).Dump(); } static IEnumerable<Person> OrderBy<TKey1, TKey2, TKey3>(this IEnumerable<Person> source, Func<Person, TKey1> selector1, Func<Person, TKey2> selector2, Func<Person, TKey3> selector3) { return source.OrderBy(selector1).ThenBy(selector2).ThenBy(selector3); } static void Dump(this IEnumerable<Person> source) { foreach (var people in source) { Console.WriteLine(people.Name); } Console.WriteLine(); } } }

投稿2016/06/07 04:59

hidori

総合スコア402

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

指定は優先順位として1st、2nd、3rd と順番通りにソートすればいいのではないでしょうか。

投稿2016/05/30 18:52

mugicya

総合スコア1046

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

yoshin

2016/05/31 04:07

回答いただきありがとうございます。 教えていただいた方法ですと、 最後に指定した第1ソートキーのみで並び替えられてしまうような気がします。 やりたいことは複合的な並び替えで、第1ソートキーで並び替えし、その並び替え結果を保持しつつ第2ソートキーで並び替えたいです。 説明下手でもうしわけありません
guest

0

無理に、1回のOrderByで済ませようとしなければそれなりにすっきり書けると思います。第3ソートキーでソートしたものを、第2ソートキーでソートし、第1ソートキーでソートすればいいです。

投稿2016/05/30 15:43

iwamoto_takaaki

総合スコア2883

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

iwamoto_takaaki

2016/05/31 01:05 編集

例えば、こんな関数。 sortNumは画面からとった項目のプルダウンの番号など。 ソートしたUserを返します。 public List<user> SortUsers(int sortNum, List<user> users){ if(sortNum == 0) return users.OrderBy(a => a.name); if(sortNum == 1) return users.OrderBy(a => a.address); if(sortNum == 2) return users.OrderBy(a => a.birth); return users; }
yoshin

2016/05/31 04:06

回答いただきありがとうございます。 教えていただいた方法ですと、 最後に指定した第1ソートキーのみで並び替えられてしまうような気がします。 やりたいことは複合的な並び替えで、第1ソートキーで並び替えし、その並び替え結果を保持しつつ第2ソートキーで並び替えたいです。 説明下手でもうしわけありません。
退会済みユーザー

退会済みユーザー

2016/05/31 10:05

もちろん最後のソートで並び替えられてしまいますが、安定ソートなので 同条件の場合は、元の順位を保持します。
iwamoto_takaaki

2016/05/31 13:50

テストしたところ安定ソートっぽいなと思いつつ、証拠になる記述を見つけられずに、.netのソースを追っていました。noricyan2さん、情報ありがとうございます。
yoshin

2016/06/01 01:52

回答いただきありがとうございました。 教えていただいた方法で実現できることも確認できました。 安定ソートという言葉も始めて知ったのでとても勉強になりました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問