導(dǎo)語
一般常用的高階函數(shù)函數(shù)有Map,Filter,Fold,Flatten,FlatMap。C#的函數(shù)式編程一般用它自帶的LINQ,LINQ我猜想它是從數(shù)據(jù)庫SQL語言的角度出發(fā)的。所以命名有些不一樣。
- Map,對應(yīng)C#的Select
- Filter,對應(yīng)C#的Where
- Fold,對應(yīng)C#的Aggregate
個人來講還是比較喜歡Map,Filter,Fold原來的這些名字,用過Lisp,Scala,Haskell的人一看就明白這些是什么意思了。但是既然C#自帶提供了,那我們就直接使用吧
Select(Map)
先看一個例子,既然C#取名Select,那我們先舉一個類似數(shù)據(jù)庫的例子把。
struct People
{
public string Name { get; set; }
public int Age { get; set; }
}
static void test1()
{
People[] PeopleList =
{
new People { Name="A", Age = 1},
new People { Name="B", Age = 2},
new People { Name="C", Age = 3},
};
PeopleList.Select(it => it.Name).ToList().ForEach(it =>
{
Console.WriteLine(string.Format("NAME:{0}", it));
});
PeopleList.Select(it => it.Age).ToList().ForEach(it =>
{
Console.WriteLine(string.Format("AGE:{0}", it));
});
}
=====運(yùn)行結(jié)果=====
NAME:A
NAME:B
NAME:C
AGE:1
AGE:2
AGE:3
==================
以上例子可以看出Select函數(shù)把People里面屬性提取出來,但是Select的功能,遠(yuǎn)大于此。其他很多語言其實叫Map,其實我更喜歡Map這個名字。Map更能貼切的形容這個功能,我們應(yīng)該 把它理解為數(shù)學(xué)概念上的集合上映射。

請看下面的例子
static void test2()
{
int[] ilist = { 1, 2, 3, 4, 5 };
ilist.Select(it => it * 2).Select(it => it.ToString()).ToList().ForEach(it =>
{
Console.WriteLine(it);
});
}
=====運(yùn)行結(jié)果=====
2
4
6
8
10
==================
這個例子可以看出,原來的list的元素全部映射為原來的兩倍。其實我們可以用Select做更復(fù)雜的映射。我們先定義一個新的類型Student。
struct Student
{
public string Name { set; get; }
public int Age { set; get; }
}
static void test3()
{
People[] PeopleList =
{
new People { Name="A", Age = 1},
new People { Name="B", Age = 2},
new People { Name="C", Age = 3},
};
PeopleList.Select(it => new Student
{
Name = "Student" + it.Name,
Age = it.Age,
}).ToList().ForEach(it =>
{
Console.WriteLine(string.Format("NAME:{0} AGE:{1}",it.Name,it.Age));
});
}
=====運(yùn)行結(jié)果=====
NAME:StudentA AGE:1
NAME:StudentB AGE:2
NAME:StudentC AGE:3
==================
如上面的例子我們把原來為People的數(shù)據(jù)類型映射為Student了。
Where(Filter)
Where像是集合的減法,過濾掉不符合條件的數(shù)據(jù)。
假設(shè)我們接到一個需求,把大于等于5歲小于15歲的人抓起來送去學(xué)校當(dāng)學(xué)生??梢韵褚韵逻@么寫。
static void test4()
{
People[] PeopleList =
{
new People { Name="A", Age = 1},
new People { Name="B", Age = 2},
new People { Name="C", Age = 5},
new People { Name="D", Age = 6},
new People { Name="E", Age = 7},
new People { Name="F", Age = 10},
new People { Name="G", Age = 20},
new People { Name="H", Age = 21},
};
PeopleList.Where(it => it.Age >= 5 && it.Age < 15).Select(it => new Student
{
Name = it.Name,
Age = it.Age
}).ToList().ForEach(it =>
{
Console.WriteLine(string.Format("NAME:{0} AGE:{1}", it.Name, it.Age));
});
}
=====運(yùn)行結(jié)果=====
NAME:C AGE:5
NAME:D AGE:6
NAME:E AGE:7
NAME:F AGE:10
==================
Fold
下面開始介紹Fold,C#里面有一個函數(shù)Aggregate,和此功能類似。但是我實在是受不了這個名字,我自己寫了一個,如下。按照C#的擴(kuò)展方法來寫的,這樣的話我就可以直接在原來是數(shù)據(jù)類型中使用了。
public static R FoldL<T, R>(this IEnumerable<T> list, Func<R, T, R> accmulator, R startValue)
{
R v = startValue;
foreach (T item in list)
{
v = accmulator(v, item);
}
return v;
}
FoldL是左折疊的意思,把一串?dāng)?shù)據(jù),從左邊開始累加在一起,至于用什么方式累加那就看accmulator函數(shù)了。startValue是初始值。有左折疊,當(dāng)然就有右折疊,但是右折疊我一般不會用到。請看下面的例子。
static void test5()
{
int[] ilist = { 1,2,3,4,5,6,7,8,9,10};
Console.WriteLine(ilist.FoldL((acc, it) => acc + it, 0));
Console.WriteLine(ilist.FoldL((acc, it) => acc + it + ",", "").TrimEnd(','));
}
=====運(yùn)行結(jié)果=====
55
1,2,3,4,5,6,7,8,9,10
==================
這個例子用了兩種累加的方法,第一種是初始值是0,然后從左邊直接相加。
第二種是初始值是""空字符串,從左邊開始,先把數(shù)據(jù)轉(zhuǎn)化為字符串,在累加之前的字符串且在后面加上逗號。最終的結(jié)果會多出一個逗號,所以我在最后加了TrimEnd(',')去掉最后的逗號,讓數(shù)據(jù)更好看點。
static void test6()
{
Student[] StudentList =
{
new Student { Name="A",Age=10},
new Student { Name="B",Age=11},
new Student { Name="C",Age=10},
new Student { Name="D",Age=13},
};
Console.WriteLine(StudentList.FoldL((acc, it) => acc + it.Age, 0) / StudentList.Length);
}
=====運(yùn)行結(jié)果=====
11
==================
這個例子可以求出所有學(xué)生的平均年齡,雖然C#有自帶的SUM函數(shù),但是我在這里還是用自己的FoldL函數(shù)來實現(xiàn)。
Flatten
Flatten函數(shù)也是很常用的,目的是把IEnumerable<IEnumerable<T>>兩層的數(shù)據(jù)變成一層IEnumerable<T>數(shù)據(jù)。這個對嵌套結(jié)構(gòu)類型的數(shù)據(jù)處理非常有用。在C#我好像沒有找到類似的,所以我自己寫了一個。Flatten英文意思是變平,我們能形象地理解這個函數(shù)的意思是把兩層IEnumerable變成一層IEnumerable,當(dāng)然它可以把三層變成兩層。Flatten的意思是把數(shù)據(jù)變平一點點,它的參數(shù)至少是接受兩層的數(shù)據(jù)。
public static IEnumerable<T> Flatten<T>(this IEnumerable<IEnumerable<T>> list)
{
foreach (IEnumerable<T> item in list)
{
foreach (T it in item)
{
yield return it;
}
}
}
如下面的例子,我們把Student這個類型添加一個課程的屬性,一個學(xué)生可能選擇多門課程。假設(shè)我的接到的需求是把學(xué)生選的課程和學(xué)生的名字打出一張表出來。請看下面的例子。
struct Student
{
public string Name { set; get; }
public int Age { set; get; }
//課程
public List<string> Courses { get; set; }
}
static void test7()
{
Student[] StudentList =
{
new Student { Name="A",Age=10,Courses=new List<string> { "數(shù)學(xué)","英語"}},
new Student { Name="B",Age=11,Courses=new List<string> { "語文"}},
new Student { Name="C",Age=10,Courses=new List<string> { "數(shù)學(xué)","生物"}},
new Student { Name="D",Age=13,Courses=new List<string> { "物理","化學(xué)"}},
};
StudentList.Select(it => it.Courses.Select(course => new
{
Name=it.Name,
Course=course
})).Flatten().ToList().ForEach(it=>
{
Console.WriteLine(string.Format("Name:{0} Course:{1}",it.Name,it.Course));
});
}
=====運(yùn)行結(jié)果=====
Name:A Course:數(shù)學(xué)
Name:A Course:英語
Name:B Course:語文
Name:C Course:數(shù)學(xué)
Name:C Course:生物
Name:D Course:物理
Name:D Course:化學(xué)
==================
這個例子中,我們還用到了C#的匿名類型。
FlatMap
這個函數(shù)其實可以理解為把數(shù)據(jù)先做Flatten再做一次Map。例子就不寫了,代碼貼這里
public static IEnumerable<R> FlatMap<T, R>(this IEnumerable<IEnumerable<T>> list, Func<T, R> convert)
{
foreach (IEnumerable<T> item in list)
{
foreach (T it in item)
{
yield return convert(it);
}
}
}
ForEach
C#只提供了List類型的ForEach函數(shù),但是沒有提供IEnumerable類型的ForEach,我們自己寫一個。代碼如下
public static void ForEach<T>(this IEnumerable<T> list, Action<T> action)
{
foreach (T item in list)
{
action(item);
}
}
GroupBy
最后介紹下C#的這個GroupBy函數(shù),非常有用。如下面例子。
struct Student
{
public string Name { set; get; }
public int Age { set; get; }
//課程
public List<string> Courses { get; set; }
public int Sex { get; set; }
public int Class { get; set; }
}
我們在Student類型里面添加多兩個屬性。性別Sex屬性(男0,女1),班級屬性Class。
需求1:把女生全部找出來
需求2:把一班的所有女生找出來
static void test8()
{
Student[] StudentList =
{
new Student { Name="A",Age=10,Sex=1,Class=1,Courses=new List<string> { "數(shù)學(xué)","英語"}},
new Student { Name="B",Age=11,Sex=0,Class=2,Courses=new List<string> { "語文"}},
new Student { Name="C",Age=10,Sex=1,Class=1,Courses=new List<string> { "數(shù)學(xué)","生物"}},
new Student { Name="D",Age=13,Sex=1,Class=2,Courses=new List<string> { "物理","化學(xué)"}},
};
StudentList.GroupBy(it => it.Sex).Where(it=>it.Key==1).ForEach(it =>
{
it.ForEach(student =>
{
Console.WriteLine("NAME:" + student.Name);
});
});
StudentList.GroupBy(it => new { SEX = it.Sex, CLASS = it.Class })
.Where(it => it.Key.SEX == 1 && it.Key.CLASS == 1).Flatten().ForEach(it=>
{
Console.WriteLine("一班女生名字:"+it.Name);
});
}
=====運(yùn)行結(jié)果=====
NAME:A
NAME:C
NAME:D
一班女生名字:A
一班女生名字:C
==================
上面的這個例子我直接把ForEach用上了,而且在需求2中我把Flatten用上了,這樣我就不需要用兩次ForEach了。
其他
我們用了這些高階函數(shù)之后,基本上一個For循環(huán)都不用再寫了。需要注意的是C#的這些函數(shù)都是惰性調(diào)用的,它們是用IEnumerable的特性來實現(xiàn)惰性調(diào)用的。這些高階函數(shù)求出來的值,在需要的時候才會真正的執(zhí)行循環(huán)體的調(diào)用。有很多細(xì)節(jié)需要理解,需要注意,后續(xù)我會詳細(xì)的舉例子來說明這些細(xì)節(jié)。