SET FORTH YOUR SET IDEARS [C#, LINQ]
WRITTEN BY ALLEN LEE
0. TABLE OF CONTENT
- 1. WHAT ARE THE DIFFERENCES?
- 2. USING SET<T> COLLECTION OF POWERCOLLECTION.
- 3. USING SET OPERATORS OF LINQ.
- 4. SET<T> COLLECTION VS. SET OPERATORS.
- 5. FURTHER CONSIDERATIONS.
- 6. EXERCISES.
1. WHAT ARE THE DIFFERENCES?
某天,我的朋友 Heng 問我有什麼工具可以比較兩個檔案夾,並找出其中不同的檔案。首先,我們來看看他的要求:
- 1) 僅需比較當前檔案夾中的檔案;
- 2) 兩個檔案夾中相同名字的檔案看作相同的檔案。
很明顯,如果把這兩個檔案夾可以看作兩個集合,
- A = { a | 第一個檔案夾中的檔案 }
- B = { b | 第二個檔案夾中的檔案 }
那麼他所找的檔案將是對這兩個集合進行一系列的集合操作所得的結果集。請看:
紅、黃兩個圓分別代表 A 和 B 兩個集合,其中1、2兩個地區就是我們要找的“不同的檔案”的集合了。使用集合標記法,這個結果集可以表示為(“'”為補集符號,全集 I = A ∪ B):
R = ((A ∪ B) ∩ (A ∩ B))' 然而,這個結果集有一個盲點,就是不加區分的把1、2兩個地區混合起來了,如果我們只需要其中一個地區的結果集呢?或者,我們需要把1、2兩個地區分別顯示到 GUI 的兩個編輯框呢?
如果我們要以 A 作為基準,找出 B 中 A 沒有的元素,那麼結果集(地區2)將表示為:
R2 = ((A ∪ B) ∩ A)' 如果我們要以 B 作為基準,找出 A 中 B 沒有的元素,那麼結果集(地區1)將表示為:
R1 = ((A ∪ B) ∩ B)' 很明顯,這一“方向”因素在具體編碼的時候是應該加以考慮的。
搞了那麼多理論,應該來點實際的了,首先,我們來看看 Set<T> Collection 是如何使用的。
2. USING SET<T> COLLECTION OF POWERCOLLECTION.
首先,我們要擷取指定路徑下的所有檔案,由於 Heng 只要求當前檔案夾裡的所有檔案,於是:
// Code #01
static string[] GetFileNamesFrom(string path)
{
if (path == null || path.Length == 0)
{
throw new ArgumentException("Parameter cannot be null or empty!");
}
if (!Directory.Exists(path))
{
throw new ArgumentException(String.Format("{0} does not exist!", path));
}
string[] fileNames = System.IO.Directory.GetFiles(path);
string[] result = new string[fileNames.Length];
for (int i = 0; i < fileNames.Length; i++)
{
result[i] = System.IO.Path.GetFileName(fileNames[i]);
}
return result;
}從 Heng 的要求中,我們可以看出只需要考慮檔案名稱,所以我們把路徑過濾掉了。
補充閱讀:
《如何判斷字串是否為空白串?[C#]》,Allen Lee
接著,我們建立 A 和 B 兩個集合:
// Code #02
Set<string> A = new Set<string>(GetFileNamesFrom(args[0]));
Set<string> B = new Set<string>(GetFileNamesFrom(args[1]));對應上面的集合討論,我們可以這樣擷取結果集:
// Code #03
Set<string> R1 = A.Difference(B);
Set<string> R2 = B.Difference(A);我們知道 R 是 R1 和 R2 的並集,如果你需要 R,你不需要先找出 R1 和 R2 再進行合并,而是直接使用 Set<T> 提供的:
// Code #04
Set<string> R = A.SymmetricDifference(B);
// OR
Set<string> R = B.SymmetricDifference(A);根據集合的性質,這兩種做法是等效的。
現在,我們來看看 LINQ 中的 Set Operators 又是如何操作的。
3. USING SET OPERATORS OF LINQ.
有了 LINQ,你可以直接“查詢”指定路徑下的檔案名稱(當然,你要保證路徑的正確性):
// Code #05
var A = from a in System.IO.Directory.GetFiles(args[0])
orderby a
select System.IO.Path.GetFileName(a);
var B = from b in System.IO.Directory.GetFiles(args[1])
orderby b
select System.IO.Path.GetFileName(b);接著,使用 Standard Query Operators 的 Except 來擷取結果集:
// Code #06
var R1 = A.Except(B);
var R2 = B.Except(A);由於 LINQ 的集合操作符(Set operators)沒有提供類似於 Set<T>.SymmetricDifference 的功能,於是,如果你需要 R 的話,你可以:
// Code #07
var R = R1.Union(R2);另外,你可以對 System.IO.Directory.GetFiles 進行一番封裝,在你的方法內檢查傳入的路徑參數。
4. SET<T> COLLECTION VS. SET OPERATORS.
由於劇情發展的需要,它們倆難免會有這樣一個碰面的情節。你會選擇哪個?這樣一個情節、這樣一個問題,使人不禁感到像在選擇一個將陪你度過下半輩子的另一半,呵呵~~~
就我個人而言,我喜歡 Set<T> Collection 的成熟,但又忘不了 Set Operators with Query 的直率。貪心的我自然希望能夠坐享齊人之福,集兩家之長啦。
Martin Hotel 的 Sales & Marketing 主管 Becky 發現最近的酒店入住率下降了,她決定對此進行一番調查。
Becky 深知留住一個老客戶的成本要比開發一個新客戶的成本低得多,於是她決定看看最近一個季度的老客戶入住率是否發生變化?如何變化?
// Code #08
var q = from c in customers
where c.Level == CustomerLevel.VIP
group c by c.CheckInDate.Month into g
select new { Month = g.Key, Count = g.Group.Count() };Becky 發現以月為單位,老客戶的入住率明顯下降了,是什麼原因呢?由於這些老客戶都是大客戶,他們只會入住五星級酒店,而本地的五星級酒店除了 Martin Hotel,就是剩下那家死對頭了。Becky 發現最近有一些人經常停留在本酒店門口,她懷疑對手派人來本酒店門口拉客,於是,她找人調查那些在本酒店門口跟這些人接觸後沒有入住本酒店的老客戶,她得到一份名單,上面列出在本酒店門口“失蹤”卻入住了對頭酒店的老客戶名字。
她把那些流失的老客戶名單和已入住的老客戶的名單加總:
// Code #09
// GetLostVipList() returns an IEnumerable<T>
var l = GetLostVipList();
var g = from c in customers
where c.Level == CustomerLevel.VIP
select c.Name;
var t = g.Union(l);
// GetVipList() returns a Set<T>
Set<Customer> vip = GetVipList();
Set<Customer> total = new Set<T>(t);
Console.WriteLine(vip.IsEqualTo(total));現在,Becky 終於知道老客戶流失的原因了,她決定起訴對頭酒店使用不正當競爭手段。
5. FURTHER CONSIDERATIONS.
我們知道,LINQ 目前還只是一個原型(prototype),而 PowerCollection 已經 release 了。然而,你不必為 LINQ 僅提供4個集合操作而煩惱,由於 LINQ 支援 Extension Methods,你可以根據需要自行擴充集合操作:
// Code #10
namespace Becky.Utils
{
public static bool IsEqualTo<T>(this IEnumerable<T> first, IEnumerable<T> second)
{
// Add some code here
}
}另外,由於集合的操作必然涉及到元素的判等,於是我們有必要對於這些工具的判等方式有一個瞭解。Set<T> Collection使用 System.Collection.Generic.IEqualityComparer<T>,而 Set Operators 則使用 Equals 和 GetHashCode 兩個方法。如果你放入集合中的是自訂對象,那麼你就要考慮這些問題了。
補充閱讀:
Object Equality and Identity in Chapter 6: Common Object Operations. Jeffrey Richter. Applied Microsoft .NET Framework Programming. Microsoft Press, 2002
最後,由於本文把重點放在集合的操作以及工具的使用上,其他一些在實際的項目中必須考慮的因素已酌情省略了。例如實際進行檔案對比時,相同名字的檔案的大小也是一個需要考慮的因素。這樣,你首先需要定義何謂不同的檔案,提取必須考慮的因素,然後抽象出一個檔案的表示,最後把“查詢”的結果投射(project)到該抽象中。
6. EXERCISES.
Heng 現在增加多一點要求,就是要考慮檔案的大小,僅當檔案名稱和檔案大小相同才看作相同的檔案。那麼,你認為 Set<T> Collection 和 Set Operators 兩個方案應該如何修改才能滿足新的需求呢?另外,如果 Heng 過幾天又可能提出新的要求,你認為你應該如何設計才能更有彈性的滿足這種需求的變化呢?