现在的位置: 首页 > 综合 > 正文

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

2012年12月27日 ⁄ 综合 ⁄ 共 4581字 ⁄ 字号 评论关闭

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) ∩ (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 的要求中,我们可以看出只需要考虑文件名,所以我们把路径过滤掉了。

接着,我们创建 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 过几天又可能提出新的要求,你认为你应该如何设计才能更有弹性的满足这种需求的变化呢?

抱歉!评论已关闭.