C#高阶函数介绍

作者: 文彬0L0 | 来源:发表于2016-11-20 19:08 被阅读634次

导语

一般常用的高阶函数函数有Map,Filter,Fold,Flatten,FlatMap。C#的函数式编程一般用它自带的LINQ,LINQ我猜想它是从数据库SQL语言的角度出发的。所以命名有些不一样。

  • Map,对应C#的Select
  • Filter,对应C#的Where
  • Fold,对应C#的Aggregate

个人来讲还是比较喜欢Map,Filter,Fold原来的这些名字,用过Lisp,Scala,Haskell的人一看就明白这些是什么意思了。但是既然C#自带提供了,那我们就直接使用吧

Select(Map)

先看一个例子,既然C#取名Select,那我们先举一个类似数据库的例子把。

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));
    });
}
=====运行结果=====
NAME:A
NAME:B
NAME:C
AGE:1
AGE:2
AGE:3
==================

以上例子可以看出Select函数把People里面属性提取出来,但是Select的功能,远大于此。其他很多语言其实叫Map,其实我更喜欢Map这个名字。Map更能贴切的形容这个功能,我们应该 把它理解为数学概念上的集合上映射。

映射

请看下面的例子

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);
    });
}
=====运行结果=====
2
4
6
8
10
==================

这个例子可以看出,原来的list的元素全部映射为原来的两倍。其实我们可以用Select做更复杂的映射。我们先定义一个新的类型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));
    });
}
=====运行结果=====
NAME:StudentA AGE:1
NAME:StudentB AGE:2
NAME:StudentC AGE:3
==================

如上面的例子我们把原来为People的数据类型映射为Student了。

Where(Filter)

Where像是集合的减法,过滤掉不符合条件的数据。
假设我们接到一个需求,把大于等于5岁小于15岁的人抓起来送去学校当学生。可以像以下这么写。

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));
    });
}
=====运行结果=====
NAME:C AGE:5
NAME:D AGE:6
NAME:E AGE:7
NAME:F AGE:10
==================

Fold

下面开始介绍Fold,C#里面有一个函数Aggregate,和此功能类似。但是我实在是受不了这个名字,我自己写了一个,如下。按照C#的扩展方法来写的,这样的话我就可以直接在原来是数据类型中使用了。

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是左折叠的意思,把一串数据,从左边开始累加在一起,至于用什么方式累加那就看accmulator函数了。startValue是初始值。有左折叠,当然就有右折叠,但是右折叠我一般不会用到。请看下面的例子。

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(','));
}  
=====运行结果=====
55
1,2,3,4,5,6,7,8,9,10
==================

这个例子用了两种累加的方法,第一种是初始值是0,然后从左边直接相加。
第二种是初始值是""空字符串,从左边开始,先把数据转化为字符串,在累加之前的字符串且在后面加上逗号。最终的结果会多出一个逗号,所以我在最后加了TrimEnd(',')去掉最后的逗号,让数据更好看点。

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);
}  
=====运行结果=====
11
==================

这个例子可以求出所有学生的平均年龄,虽然C#有自带的SUM函数,但是我在这里还是用自己的FoldL函数来实现。

Flatten

Flatten函数也是很常用的,目的是把IEnumerable<IEnumerable<T>>两层的数据变成一层IEnumerable<T>数据。这个对嵌套结构类型的数据处理非常有用。在C#我好像没有找到类似的,所以我自己写了一个。Flatten英文意思是变平,我们能形象地理解这个函数的意思是把两层IEnumerable变成一层IEnumerable,当然它可以把三层变成两层。Flatten的意思是把数据变平一点点,它的参数至少是接受两层的数据。

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这个类型添加一个课程的属性,一个学生可能选择多门课程。假设我的接到的需求是把学生选的课程和学生的名字打出一张表出来。请看下面的例子。

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> { "数学","英语"}},
        new Student { Name="B",Age=11,Courses=new List<string> { "语文"}},
        new Student { Name="C",Age=10,Courses=new List<string> { "数学","生物"}},
        new Student { Name="D",Age=13,Courses=new List<string> { "物理","化学"}},
    };
    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));
    });
}  
=====运行结果=====
Name:A Course:数学
Name:A Course:英语
Name:B Course:语文
Name:C Course:数学
Name:C Course:生物
Name:D Course:物理
Name:D Course:化学
==================

这个例子中,我们还用到了C#的匿名类型。

FlatMap

这个函数其实可以理解为把数据先做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函数,但是没有提供IEnumerable类型的ForEach,我们自己写一个。代码如下

public static void ForEach<T>(this IEnumerable<T> list, Action<T> action)
{
    foreach (T item in list)
    {
        action(item);
    }
}  

GroupBy

最后介绍下C#的这个GroupBy函数,非常有用。如下面例子。

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> { "数学","英语"}},
        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> { "数学","生物"}},
        new Student { Name="D",Age=13,Sex=1,Class=2,Courses=new List<string> { "物理","化学"}},
    };
    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);
        });
}  
=====运行结果=====
NAME:A
NAME:C
NAME:D
一班女生名字:A
一班女生名字:C
==================

上面的这个例子我直接把ForEach用上了,而且在需求2中我把Flatten用上了,这样我就不需要用两次ForEach了。

其他

我们用了这些高阶函数之后,基本上一个For循环都不用再写了。需要注意的是C#的这些函数都是惰性调用的,它们是用IEnumerable的特性来实现惰性调用的。这些高阶函数求出来的值,在需要的时候才会真正的执行循环体的调用。有很多细节需要理解,需要注意,后续我会详细的举例子来说明这些细节。

相关文章

  • C#高阶函数介绍

    导语 一般常用的高阶函数函数有Map,Filter,Fold,Flatten,FlatMap。C#的函数式编程一般...

  • python函数式编程

    高阶函数 把函数作为参数传入,这样的函数称为高阶函数,函数式编程就是指这种高度抽象的编程范式。与js相似,与c#中...

  • React高阶组件

    介绍 将高阶组件之前先讲高阶函数,所谓高阶函数,就是接受一个或多个函数作为输入,或者输出一个一个函数的函数。而高阶...

  • 高阶函数介绍

    1.map函数 (Return an Array containing the results of mappin...

  • scala(六) 高阶函数

    介绍 高阶函数:以函数作为参数或返回值的方法(函数)称为高阶函数。我的理解是高阶函数是一种思想,它的作用能让我们的...

  • Kotlin学习 5 -- 高阶函数

    本篇文章主要介绍以下几个知识点:高阶函数内联函数noinline 与 crossinline高阶函数的应用内容参考...

  • Python函数式介绍一 - 高阶函数

    Python函数式介绍一 - 高阶函数Python函数式介绍二 - 链式调用 最近为了给朋友推广Python函数式...

  • python中的高阶函数

    高阶函数:高阶函数是指包含函数参数的函数,在函数中利用另一个函数进行操作。本文将分别介绍map、reduce、fi...

  • 高阶函数

    上一篇已经介绍到了高阶函数map,本节继续讲解其余的几个高阶函数 map()函数 参数:接收一个函数 f 和一个 ...

  • python学习(三)函数式编程

    高阶函数 函数也是变量,函数参数为函数的函数,称作高阶函数 自定义高阶函数 内建高阶函数 map/reducema...

网友评论

  • 5ef945f83583:C#的SelectMany相当于是FlatMap
    文彬0L0:@doubleaching 谢谢,当时接触C#没多久,不熟悉。后面知道了SelectMany,Aggregate,Join这些函数,再也没用自己写的一些扩展函数了。当时我就纳闷怎么FlatMap这么重要的函数LINQ没有?只是我不熟悉罢了
  • 成功的失败者:我想问工作了,还有时间学习与工作无关的知识吗?还有时间写长长的博客吗?
    文彬0L0:@成功的失败者 时间是抽出来的,程序员注定要学习大量的东西的,吸收编程思想,无论是工作相关还是无关,想要提高能力必须学习。事实上,对我自己能力提高最大的反倒是工作之外学到的东西。平时有记录的习惯的话,长博客不过是把以前记录的东西整理一下,花的精力不算太多但也不少
  • 良森:厉害:+1:🏻写了这么多,找个时间详细看看:smile:

本文标题:C#高阶函数介绍

本文链接:https://www.haomeiwen.com/subject/ivxepttx.html