[雜耍] 十二生肖排序

  • 1360
  • 0
  • 2018-07-13

這篇的主角是 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> 搞混了。

IComparer<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 決定大小,簡單到好笑。

實作 ICompare<T>

整個實作程式碼沒有幾行,如下所示:
 

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));
    }
}
為何 zodiacs 這個 List 要宣告為 static?

這牽涉到一個假設 -- 這個 ZCompare Class 不會只被用到一次。如果每次使用這個類別產生執行個體,就得重新產生一次這個 List<string>,那不是太勞累的嗎?

決定 Compare 方法回傳值的方式

反正就是大於零、等於零、小於零,沒有人規定非得回傳 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