引爆你的集合靈感 [C#, LINQ]


引爆你的集合靈感 [C#, LINQ]

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 | 第二個文件夾中的文件 }

那么他所找的文件將是對這兩個集合進行一系列的集合操作所得的結果集。請看下圖:

Venn diagram

紅、黃兩個圓分別代表 A 和 B 兩個集合,其中1、2兩個區域就是我們要找的“不同的文件”的集合了。使用集合表示法,這個結果集可以表示為(“'”為補集符號,全集 I = A ∪ B):

R = (A ∩ B)'

然而,這個結果集有一個盲點,就是不加區分的把1、2兩個區域混合起來了,如果我們只需要其中一個區域的結果集呢?或者,我們需要把1、2兩個區域分別顯示到 GUI 的兩個編輯框呢?

如果我們要以 A 作為基准,找出 B 中 A 沒有的元素,那么結果集(區域2)將表示為:

R2 = A'

如果我們要以 B 作為基准,找出 A 中 B 沒有的元素,那么結果集(區域1)將表示為:

R1 = B'

很明顯,這一“方向”因素在具體編碼的時候是應該加以考慮的。

搞了那么多理論,應該來點實際的了,首先,我們來看看 Set 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 的要求中,我們可以看出只需要考慮文件名,所以我們把路徑過濾掉了。

接着,我們創建 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 提供的:

//  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 .SymmetricDifference 的功能,於是,如果你需要 R 的話,你可以:

//  Code #07

var R 
=  R1.Union(R2);

另外,你可以對 System.IO.Directory.GetFiles 進行一番包裝,在你的方法內檢查傳入的路徑參數。

 

4. SET<T> COLLECTION VS. SET OPERATORS.

由於劇情發展的需要,它們倆難免會有這樣一個碰面的情節。你會選擇哪個?這樣一個情節、這樣一個問題,使人不禁感到像在選擇一個將陪你度過下半輩子的另一半,呵呵~~~

就我個人而言,我喜歡 Set 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
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
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 Collection使用 System.Collection.Generic.IEqualityComparer ,而 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 Collection 和 Set Operators 兩個方案應該如何修改才能滿足新的需求呢?另外,如果 Heng 過幾天又可能提出新的要求,你認為你應該如何設計才能更有彈性的滿足這種需求的變化呢?


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2020 ITdaan.com