21
18

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2019-11-01

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である場合は検討ください。

21
18
7

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
21
18

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?