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

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

ただいまの
回答率

88.33%

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

解決済

回答 5

投稿

  • 評価
  • クリップ 2
  • VIEW 17K+

yoshin

score 37

前提・実現したいこと

ラムダ式を使って複数のソートキーを組み合わせて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#

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 5

+2

static IOrderedEnumerable<TSource> MyOrderBy<TSource>(this IEnumerable<TSource> list, params Func<TSource, string>[] orders)
{
    if (orders.Length == 0)
        throw new InvalidOperationException();

    IOrderedEnumerable<TSource> result = null;
    foreach (var item in orders)
    {
        if (result == null)
            result = list.OrderBy(source => item(source));
        else
            result = result.ThenBy(source => item(source));
    }

    return result;
}

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

var users = new List<User>();

var result = users.MyOrderBy(
    user => user.address, 
    user => user.birth, 
    user => user.name);

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

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

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/06/01 10:54

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

    キャンセル

  • 2016/06/07 13:57

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

    キャンセル

checkベストアンサー

+1

こんにちは.

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

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

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

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

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

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/05/31 13:13

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

    キャンセル

  • 2016/05/31 13:24 編集

    ソートを行う行は,変数の数分だけで済みます.

    たとえばソート手順配列を文字列で作るとして,この配列に "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 してリスト上書き}

    の部分を追加するだけで済むのではないかと.

    キャンセル

  • 2016/05/31 13:41 編集

    上記の事をコードで表すと,このような関数になります.

    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の内容で全て対応できます.

    キャンセル

0

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

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/05/31 19:05

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

    キャンセル

  • 2016/05/31 22:50

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

    キャンセル

  • 2016/06/01 10:52

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

    キャンセル

0

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

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/05/31 13:07

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

    キャンセル

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();
        }
    }
}

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

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

  • ただいまの回答率 88.33%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る