.Net Sort
今天來複習一下 .Net 中排序的各種用法,首先是最簡單的由小到大排序
使用 Sort 方法可以直接將資料做正向排序,由於 Generic List 集合是屬於可變集合,因此 Sort 方法呼叫後會直接修改 myList 內容。
var myList = new List<int>() { 1, 7, 4, 2, 6, 3 };
myList.Sort();
另外 Immutable List 集合是屬於不可變集合,因此 Sort 方法不會修改原集合,而是回傳一個新的 ImmutableList 物件,因此需要重新指派。
var myList = ImmutableList.Create(1, 7, 4, 2, 6, 3);
myList = myList.Sort();
反向排序也很簡單,可以先呼叫 Sort 方法,之後在呼叫一次 Reverse 方法,可讀性會比較高。 也可以直接傳入客製的比較器,能夠做到一步倒序。
var myList = new List<int>() { 1, 7, 4, 2, 6, 3 };
myList.Sort();
myList.Reverse();
var myList = new List<int>() { 1, 7, 4, 2, 6, 3 };
myList.Sort((a, b) => b.CompareTo(a));
LINQ Lambda Expression And LINQ Query Expression
使用 Lambda 表達式的 OrderBy、ThenBy、OrderByDescending、ThenByDescending擴充方法,
不直接修改原集合,而是回傳 OrderedEnumerable 集合,特性是擁有 Lazy 特性,代表只有在 enumerate 結果的時候才會真正進行排序。
void Main()
{
var users = new List<User>()
{
new User("Charlie", 31),
new User("Bob", 35),
new User("Eve", 34),
new User("David", 24),
new User("Alice", 24)
};
// 先按照 Age 排,再按照 Name 排
var orderUser = users
.OrderBy(p => p.Age)
.ThenBy(p => p.Name);
// 先按照 Age 倒序排,再按照 Name 倒序排
var orderUser1 = users
.OrderByDescending(p => p.Age)
.ThenByDescending(p => p.Name);
// 因為有 Lazy 特性,所以需要增加下面這兩行正式取值,否則就不會做任何事情。
Console.WriteLine($"{orderUser.First().Name} ({orderUser.First().Age})");
Console.WriteLine($"{orderUser1.First().Name} ({orderUser1.First().Age})");
}
public record User(string Name, int Age);
另外也可以使用 Query 表達式,在一些複雜的排序場合下可讀性會比較高
void Main()
{
var users = new List<User>()
{
new User("Charlie", 31),
new User("Bob", 35),
new User("Eve", 34),
new User("David", 24),
new User("Alice", 24)
};
// 先按照 Age 排,再按照 Name 排
var orderUser = from p in users
orderby p.Age, p.Name
select p;
// 先按照 Age 倒序排,再按照 Name 倒序排
var orderUser1 = from p in users
orderby p.Age descending, p.Name descending
select p;
// 因為有 Lazy 特性,所以需要增加下面這兩行正式取值,否則就不會做任何事情。
Console.WriteLine($"{orderUser.First().Name} ({orderUser.First().Age})");
Console.WriteLine($"{orderUser1.First().Name} ({orderUser1.First().Age})");
}
public record User(string Name, int Age);
String Sort
在 C# 中排序字串時,同樣可以使用 Sort() 方法,需要注意的是,預設排序會依照系統的 CultureInfo 進行,並不一定是 ASCII 排序。
因此 C# 提供了 StringComparer 類別,能讓我們統一控制不同文化排序方式,例如:
- StringComparer.Ordinal:按 ASCII 值排序,與文化無關。
- StringComparer.OrdinalIgnoreCase:按 ASCII 排序,忽略大小寫。
- StringComparer.CurrentCulture:根據目前系統文化排序。
- StringComparer.CurrentCultureIgnoreCase:根據目前系統文化排序,忽略大小寫。
- StringComparer.InvariantCulture:根據固定文化排序,不參考系統文化。
- StringComparer.InvariantCultureIgnoreCase:根據固定文化排序,不參考系統文化,忽略大小寫。
void Main()
{
var words = new List<string>() { "i", "love", "csharp", "i", "love", "sorting" };
words.Sort(StringComparer.Ordinal);
foreach (var element in words)
{
Console.WriteLine(element);
}
}
// 輸出
csharp
i
i
love
love
sorting
我們這邊再把第一個 love 改成大寫形式,在進行排序會發現大寫的 Love 變到第一位了,這是因為大寫的 L 為 76 小寫的 c 為 99
所以這時就會把 Love 拉到第一位
void Main()
{
var words = new List<string>(){ "i", "Love", "csharp", "i", "love", "sorting"};
words.Sort(StringComparer.Ordinal);
foreach (var element in words)
{
Console.WriteLine(element);
}
}
// 輸出
Love
csharp
i
i
love
sorting
當然也可使用 StringComparer.OrdinalIgnoreCase 也是以 ASCII 為基礎但是會忽略掉大小寫
void Main()
{
var words = new List<string>() { "i", "love", "csharp", "i", "love", "sorting" };
words.Sort(StringComparer.OrdinalIgnoreCase);
foreach (var element in words)
{
Console.WriteLine(element);
}
}
// 輸出
csharp
i
i
love
love
sorting
使用多條件排序(單字出現頻率 + ASCII) 搭配 LINQ,最後挑選前兩名。
void Main()
{
var words = new List<string> { "love", "i", "csharp", "love", "i", "sorting", "sorting", "sorting" };
// 統計每個單字的出現頻率
var freqMap = words
.GroupBy(w => w)
.ToDictionary(g => g.Key, g => g.Count());
// 多條件排序:先依頻率再依 ASCII 字母排序
var topTwo = freqMap
.OrderByDescending(kv => kv.Value) // 依頻率排序
.ThenBy(kv => kv.Key, StringComparer.Ordinal) // 頻率相同時依 ASCII 排序
.Take(2) // 取前兩名
.Select(kv => kv.Key) // 只取 Key
.ToList();
// 輸出結果
topTwo.ForEach(Console.WriteLine);
}
在 .Net 10 提供了 CompareOptions 讓建立比較器更加容易,例如 CompareOptions.NumericOrdering,
適合用來排序文字中帶有數字的資料。
void Main()
{
var words = new List<string> { "file2", "file1", "file4", "file3"};
var comparer = StringComparer.Create(
CultureInfo.InvariantCulture,
CompareOptions.NumericOrdering);
var sorted = words.OrderBy(x => x, comparer);
sorted.ToList().ForEach(Console.WriteLine);
}