C#のソート方法って色々ありますよね。
検索かけたら、結局どれ使えばいいの?って私はなりました。
一項目だけ昇順にソートできればそれでいいって場合や、複数項目を指定したい…って場合あります。
私としては、複数項目をソートしたい場合はLINQが良いと思いました。
超簡単なコンソールアプリ作って理由を説明していきます。
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を使用したソースコードがこちら。
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で国語の成績を降順に評価し並び替えます。この場合は省略していますが、国語の値を返しています。
以上のコードでコンソールアプリを構成し、実行した結果がこちら。
きちんとルール通り並べ替えられていますね。
ちょっと自分語り入りますが、現在私はこの並び替えルール以上に複雑怪奇な画面表示用データ並び替えを要求されています。
その蹴飛ばしたくなる要求を答えることができたのは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である場合は検討ください。