Map, Filter, Reduce in iOS

这三个函数在编程语言中非常的实用,所以今天非常有必要跟大家探讨一下这三个函数的使用方法,我们拿iOS中的使用为例。

在正式介绍这三个函数之前,我们先提供一个资源:一个班级所有学生的一次考试成绩。

首先,我们先创建学生这个类型:
Student.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@interface Student : NSObject
@property (nonatomic, strong) NSString * name;
@property (nonatomic, assign) NSInteger math;
@property (nonatomic, assign) NSInteger chinese;
@property (nonatomic, assign) NSInteger english;

+ (instancetype)newStudentWithName:(NSString *)name
math:(NSInteger)math
chinese:(NSInteger)chinese
english:(NSInteger)english;
@end

@implementation Student

+ (instancetype)newStudentWithName:(NSString *)name
math:(NSInteger)math
chinese:(NSInteger)chinese
english:(NSInteger)english
{
Student * student = [[Student alloc] init];
student.name = name;
student.math = math;
student.chinese = chinese;
student.english = english;
return student;
}
1
2
3
4
5
6
struct Student {
let name: String
let math: Int
let chinese: Int
let english: Int
}

然后,我们创建一个班级的所有学生成绩信息(这里只提供5位同学):

1
2
3
4
5
6
7
NSArray * students = @[
[Student newStudentWithName:@"Helen" math:79 chinese:98 english:89],
[Student newStudentWithName:@"Jack" math:90 chinese:80 english:76],
[Student newStudentWithName:@"Bomb" math:98 chinese:43 english:79],
[Student newStudentWithName:@"Joke" math:56 chinese:87 english:30],
[Student newStudentWithName:@"Marry" math:99 chinese:60 english:32]
];
1
2
3
4
5
6
7
let students = [
Student(name: "Helen", math: 79, chinese: 98, english: 89),
Student(name: "Jack", math: 90, chinese: 80, english: 76),
Student(name: "Bomb", math: 98, chinese: 43, english: 79),
Student(name: "Joke", math: 56, chinese: 87, english: 30),
Student(name: "Marry", math: 99, chinese: 60, english: 32)
]

接下来,正式介绍这三个函数的作用:

Map

Map:一个数组到另一个数组的映射。

俗称将一个数组通过一定的运算法则转化为另一个数组,这两个数组里的元素个数是一样的,但是元素类型是可以不一样的。

[a] -> [b]

以上面的例子,要想取出所有同学的数学成绩,这个时候就可以考虑到map:

1
2
3
let maths = students.map { (student) -> Int in
return student.math
}

其中students为原数组,maths为目标数组,map后面的大括号为转换法则。

这里用了Swift里的Closures

遗憾的是,在Objective-C里,没有map,没有。。。
当然,我们也可以使用原始的方法for去遍历,也可以采用Objective-C里的enumerate系列方法,但是,当爱上了map,filter,reduce以后,我希望我直接调用的还是这几个函数,于是,我们来扩展NSArray类:

1
2
3
4
5
6
// NSArray+Founctional.h
@interface NSArray (Functional)

- (NSArray *)map:(id (^) (id obj))block;

@end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// NSArray+Founctional.m
@implementation NSArray (Functional)
- (NSArray *)map:(id (^) (id obj))block {
NSMutableArray *array = [[NSMutableArray alloc] init];
[self enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
id element = block(obj);
if (element) {
[array addObject:element];
}
}];
return array;
}

@end

于是,还是上面的那个例子,在Objective-C中的实现为:

1
2
3
NSArray * maths = [students map:^id(Student * obj) {
return @(obj.math);
}];

当然,上面的原数组是创建的一些假数据,如果你有一些真实的数据,也可以通过map来将数据源转化为Student模型。

Filter

Filter:过滤。

一个数组通过一定的运算法则过滤掉相应的元素,成为另一个数组,这两个数组里的元素个数是不一样的,但是元素类型是一样的。

[a] -> [a]
eg.我们用filter来取出所有语文成绩及格的同学:

1
2
3
let passStudents = students.filter { (student) -> Bool in
return student.chinese >= 60
}

同样的,students为原数组,passStudents为目标数组,filter后面的大括号为过滤法则。

在Objective-C中还是没有filter函数,我们还是以同样的方法来扩展NSArray:
在上面的NSArray+Founctional.h中添加:

1
- (NSArray *)filter:(BOOL (^) (id obj))block;

在NSArray+Founctional.m中添加:

1
2
3
4
5
6
7
8
9
10
- (NSArray *)filter:(BOOL (^) (id obj))block {
NSMutableArray *array = [[NSMutableArray alloc] init];
[self enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
BOOL element = block(obj);
if (element) {
[array addObject:obj];
}
}];
return array;
}

上面的例子在oc中样式:

1
2
3
NSArray * passStudents = [students filter:^BOOL(Student * obj) {
return obj.chinese >= 60;
}];

Reduce

Reduce:以一个数组为数据源,通过一定的运算法则,最后合成另一种类型,在有些编程语言中起名foldr.

[a] -> b

eg.我们用reduce来算出所有学生的英语成绩之和:

1
2
3
let totalScore = students.reduce(0) { (sum, student) -> Int in
return sum + student.english
}

标:reduce后面的0为初始值,sum为每次循环之前算出的总和,student为本次循环的学生模型。

组合使用

当然,这三个函数也可以组合起来使用,来完成稍微复杂点的需求。
eg.我们要获取语文成绩及格的的所有学生姓名:

1
2
3
4
5
students.filter { (student) -> Bool in
return student.chinese >= 60
}.map { (student) -> String in
return student.name
}

eg.三门成绩都及格的所有同学的数学成绩之和:

1
2
3
4
5
6
7
students.filter { (student) -> Bool in
return student.math >= 60
&& student.chinese >= 60
&& student.english >= 60
}.reduce(0) { (sum, student) -> Int in
return sum + student.english
}