C#
LINQ
15
どのような問題がありますか?

この記事は最終更新日から1年以上が経過しています。

投稿日

更新日

ちょっと複雑な並べ替えをするときはLINQが楽でよい

C#のソート方法って色々ありますよね。
検索かけたら、結局どれ使えばいいの?って私はなりました。

一項目だけ昇順にソートできればそれでいいって場合や、複数項目を指定したい…って場合あります。
私としては、複数項目をソートしたい場合はLINQが良いと思いました。
超簡単なコンソールアプリ作って理由を説明していきます。

Student.cs
using System;
using System.Collections.Generic;
using System.Text;

namespace Sort
{
    class Student
    {
        // 生徒番号
        public string StudentNumber { get; set; }

        // 名前
        public string Name { get; set; }

        // 血液型
        public string BloodType { get; set; }

        // 組
        public string Department { get; set; }

        // 国語
        public int National { get; set; }

        // 算数
        public int Mathematics { get; set; }

        // コンストラクタ
        public Student(string studentNumber, string name, string bloodType, string department, int national, int mathematics)
        {
            StudentNumber = studentNumber;
            Name = name;
            BloodType = bloodType;
            Department = department;
            National = national;
            Mathematics = mathematics;
        }
    }
}

上のようなクラスを用意しました。
このクラスのデータを以下のようなルールでソートしたいとします。

・最初に、血液型をO型→A型→AB型→B型の順でソート
・同じ血液型の場合は、組をB組→C組→A組の順でソート
・組も同じ場合は、国語の点数を降順にソート

このような要求があったらどうでしょう?Sortメソッドを使う方法だと結構面倒なんじゃないでしょうか。
少なくとも私は、Sortメソッドを使って引数にComparisonデリゲートを使うやり方を試したら非常につらかったです。

ここでLINQが登場するわけです。
上で挙げたルールを満たすLINQを使用したソースコードがこちら。

Program.cs
using System;
using System.Collections.Generic;
using System.Linq;

namespace Sort
{
    class Program
    {
        static void Main(string[] args)
        {
            List<Student> lists = new List<Student>();
            IEnumerable<Student> query;

            lists.Add(new Student("00010", "和田", "B", "A", 55, 30));
            lists.Add(new Student("00003", "ダル", "B", "B", 50, 30));
            lists.Add(new Student("00002", "田中", "O", "A", 40, 70));
            lists.Add(new Student("00007", "野茂", "A", "B", 15, 50));
            lists.Add(new Student("00006", "黒田", "AB", "A", 55, 10));
            lists.Add(new Student("00009", "伊良部", "AB", "C", 85, 90));
            lists.Add(new Student("00008", "長谷川", "B", "C", 60, 45));
            lists.Add(new Student("00005", "前田", "O", "A", 60, 30));
            lists.Add(new Student("00004", "菊池", "O", "B", 50, 20));
            lists.Add(new Student("00001", "平野", "A", "C", 30, 40));
            lists.Add(new Student("00011", "大家", "AB", "B", 25, 45));
            lists.Add(new Student("00012", "佐々木", "O", "A", 90, 80));

            query = lists
                 .OrderBy(value =>
                 {
                     int result = 0;

                     switch (value.BloodType)
                     {
                         case "A":
                             result = 1;
                             break;
                         case "B":
                             result = 3;
                             break;
                         case "O":
                             result = 0;
                             break;
                         case "AB":
                             result = 2;
                             break;
                     }

                     return result;
                 })
                 .ThenBy(value =>
                 {
                     int result = 0;
                     switch (value.Department)
                     {
                         case "A":
                             result = 2;
                             break;
                         case "B":
                             result = 0;
                             break;
                         case "C":
                             result = 1;
                             break;
                     }
                     return result;
                 })
                 .ThenByDescending(value => value.National);

            Console.WriteLine("生徒番号\t名前\t血液型\tクラス\t国語\t算数");
            foreach (Student a in query)
            {
                Console.WriteLine("{0}\t\t{1}\t{2}\t{3}\t{4}\t{5}"
                    , a.StudentNumber
                    , a.Name
                    , a.BloodType
                    , a.Department
                    , a.National
                    , a.Mathematics
                    );
            }
        }
    }
}

適当にデータをリストに追加したのち、そのデータをソート、表示しております。

リストデータの値を調べ、まず血液型がA型の場合は1、Bは3、Oは0、ABは2をresultに代入、リターンします。
リターンしたデータをOrderByは昇順で並べ替えてくれます。つまり、O型→A型→AB型→B型の順ですね。
次に、ThenBy(OrderBy,OrderByDescendingを使うのは最初だけ)でA組所属であれば2、Bは0、Cは1、というように組のソートもOK。
最後にThenByDescendingで国語の成績を降順に評価し並び替えます。この場合は省略していますが、国語の値を返しています。

以上のコードでコンソールアプリを構成し、実行した結果がこちら。

5.png

きちんとルール通り並べ替えられていますね。
ちょっと自分語り入りますが、現在私はこの並び替えルール以上に複雑怪奇な画面表示用データ並び替えを要求されています。
その蹴飛ばしたくなる要求を答えることができたのはLINQの並び替えが強力だったためです。
Sortメソッドも試しましたが、2つ目以降の項目(今回でいうと"組")の並び替えがどうも辛いというか苦手というか。
ThenByの存在がLINQソートの強みですね。

LINQは様々な言語で使えるようです。
駆け出しエンジニアの皆さん、ソートに困ったらLINQ使ってみてください。

追記

albireoさんからコメントで保守性の高い書き方を教えて頂きました。


// 血液型のソート
.OrderBy(value => Array.IndexOf(new[] { "O", "A", "AB", "B" }, value.BloodType))
// 組のソート
.ThenBy(value => Array.IndexOf(new[] { "B", "C", "A" }, value.Department))

以上のような書き方にすることで、
今回出したソートのルールを守ったうえで美しくメンテしやすいコードになりました。

追記2

htsignさんからタプルを利用する方法を教えて頂きました。


// タプルの定義
int orderBloodType(Student student) =>
    student.BloodType switch
    {
        "A" => 1,
        "B" => 3,
        "O" => 0,
        "AB" => 2,
        _ => throw new ArgumentOutOfRangeException(nameof(Student.BloodType)),
    };
int orderDepartment(Student student) =>
    student.Department switch
    {
        "A" => 2,
        "B" => 0,
        "C" => 1,
        _ => throw new ArgumentOutOfRangeException(nameof(Student.Department)),
    };

// ソート処理
var query = lists.OrderBy(x => (orderBloodType(x), orderDepartment(x), -x.National));
/*
一括でやらない場合は
var query = lists.OrderBy(x => orderBloodType(x))
                 .ThenBy(x => orderDepartment(x))
                 .ThenByDescending(x => x.National);
*/

例外処理まで含めて簡潔に書けますね。
C#のバージョンが8.0である場合は検討ください。

新規登録して、もっと便利にQiitaを使ってみよう

  1. あなたにマッチした記事をお届けします
  2. 便利な情報をあとで効率的に読み返せます
ログインすると使える機能について

コメント

この記事にコメントはありません。
あなたもコメントしてみませんか :)
新規登録
すでにアカウントを持っている方はログイン
記事投稿イベント開催中
フロントエンドの開発効率を向上するヒントを教え合おう!
~
PHP強化月間~開発する上で知っておくべき知見を共有しよう~
~
15
どのような問題がありますか?
新規登録して、Qiitaをもっと便利に使ってみませんか

この機能を利用するにはログインする必要があります。ログインするとさらに下記の機能が使えます。

  1. ユーザーやタグのフォロー機能であなたにマッチした記事をお届け
  2. ストック機能で便利な情報を後から効率的に読み返せる
新規登録ログイン
ストックするカテゴリー