這篇的主角是 Enumerable.OrderBy<TSource, TKey>(IEnumerable<TSource>, Func<TSource, TKey>, IComparer<TKey>)
『給定一個內容為十二生肖的 List<string>,使用 Emumerable.OrderBy 排序,結果要符合國人對於十二生肖慣用順序。』這是一個簡單的問題,同時也是有趣的問題。
粗略地來說,在沒有特定狀況 (文化特性、忽略大小寫分別之類),字串的排序是依照著字碼順序排列的;很不幸十二生肖並不是這樣排的。假設撰寫以下的程式碼:
List<string> _zodiacs
= new List<string>
{
"鼠", "牛", "虎", "兔", "龍" , "蛇", "馬", "羊" , "猴", "雞", "狗", "豬"
};
foreach (var item in _zodiacs.OrderBy((x) => x))
{
Console.Write(item);
}
得到的結果將會是 ~~ 牛羊兔狗虎馬蛇猴鼠豬龍雞。所以這樣使用 OrderBy 是行不通的。
所幸 Enumerable.OrderBy<TSource, TKey> 有個多載可以傳入 IComparer<TKey> 作為自訂排序的標準。既然我們要排序的型別,那自然是要實作 IComparer<string>;注意,請不要和 IComparable<T> 搞混了。
在 MSDN 文件是這樣講的:定義類型會實作以比較兩個物件的方法。這個介面只包含一個方法 int Compare(T x, T y) ,這個回傳的 int 很有趣:
(1)若 x > y 則實作內容要回傳大於零的int值,意即你可以回傳 1,也可以回傳 2 或任何大於零的 int。
(2)若 x = y 則實作內容要回傳零。
(3)若 x < y 則實作內容要回傳小於零的int值,所以你可以回傳 -1、-2 等等任何小於零的 int。
最簡單的方式,建立一個依照順序的 List<string>,就可以靠該生肖在 List 中的 index 決定大小,簡單到好笑。
整個實作程式碼沒有幾行,如下所示:
public class ZCompare : IComparer<string>
{
private static List<string> zodiacs = new List<string>()
{
"鼠", "牛", "虎", "兔", "龍" , "蛇", "馬", "羊" , "猴", "雞", "狗", "豬"
};
public int Compare(string x, string y)
{
return (zodiacs.IndexOf(x) - zodiacs.IndexOf(y));
}
}
這牽涉到一個假設 -- 這個 ZCompare Class 不會只被用到一次。如果每次使用這個類別產生執行個體,就得重新產生一次這個 List<string>,那不是太勞累的嗎?
反正就是大於零、等於零、小於零,沒有人規定非得回傳 1, 0, -1 不可,所以用該生肖位於列表中的索引值(index)相減正好符合需求。
static void Main(string[] args)
{
List<string> list
= new List<string> { "牛", "豬", "鼠", "羊", "狗" };
foreach (var item in list.OrderBy((x) => x, new ZCompare()))
{
Console.WriteLine(item);
}
Console.ReadLine();
}
很簡單的程式碼,卻藏著一些細緻的道理。範例放在 https://github.com/billchungiii/OrderZodiacsSample