C#高階函數(shù)介紹

導(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é)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容