在swift中给Array提供了很多函数式的操作,这些操作其实在Objective - C的ReactiveCocoa开源库中也有对应的操作。这些操作大大减少了代码量,同时也让很多同学非常难以理解,学习嘛,不仅要知其然,还要知其所以然。
Map
引子:我们通常会遇到这样一个问题,需要对数组中的所有元素做一个操作,比如+1。如果不知道map操作或者不使用毛,通常我们会这么做:
let numbers = [1, 2, 3, 4]
var result = []()
for i in numbers {
result.append(i + 1)
}
print(result)
// [2, 4, 6, 8]
使用map以后,简直so easy,一行代码搞定
let numbers = [1, 2, 3, 4]
let result = numbers.map{$0 + 1}
我们再看看API文档:
/// Returns an array containing the results of mapping the given closure
/// over the sequence's elements.
///
/// In this example, `map` is used first to convert the names in the array
/// to lowercase strings and then to count their characters.
///
/// let cast = ["Vivien", "Marlon", "Kim", "Karl"]
/// let lowercaseNames = cast.map { $0.lowercaseString }
/// // 'lowercaseNames' == ["vivien", "marlon", "kim", "karl"]
/// let letterCounts = cast.map { $0.characters.count }
/// // 'letterCounts' == [6, 6, 3, 4]
///
/// - Parameter transform: A mapping closure. `transform` accepts an
/// element of this sequence as its parameter and returns a transformed
/// value of the same or of a different type.
/// - Returns: An array containing the transformed elements of this
/// sequence.
public func map(_ transform: @noescape Element throws -> T) rethrows -> [T]
通过文档的解释,我们可以知道,其实map函数可以对数组里面的元素进行遍历,并且你可以在遍历的同时加上自己的转换规则(transform),返回的结果是转换后的新元素组成的新数组;
关于转换这里,同学们 要注意以下,这里被传入的参数是一个非逃逸闭包(@noescape),也就是说这个闭包只能在map函数里面执行,所以在里面做一些操作的时候注意作用域问题,笔者之前被坑过!!!
通过API文档我们可以猜测一下,那么大致实现如下:
func map(array: [T], f: T -> U) -> [U] {
var result = [U]()
for i in numbers {
result.append(i)
}
return result
}
FlatMap
FlatMap其实很多人觉得他们俩是一样的,在很多操作上看起来确实是一样的
let numbers = [1, 2, 3, 4]
let result = numbers.flatMap{$0 + 1}
// result -> [2, 4, 6, 8]
这样看起来是一样的,那么到底哪里不同呢?咱们先从API文档里面找找看:
/// Returns an array containing the non-`nil` results of calling the given
/// transformation with each element of this sequence.
///
/// Use this method to receive an array of nonoptional values when your
/// transformation produces an optional value.
///
/// In this example, note the difference in the result of using `map` and
/// `flatMap` with a transformation that returns an optional `Int` value.
///
/// let possibleNumbers = ["1", "2", "three", "///4///", "5"]
///
/// let mapped: [Int?] = numbers.map { str in Int(str) }
/// // [1, 2, nil, nil, 5]
///
/// let flatMapped: [Int] = numbers.flatMap { str in Int(str) }
/// // [1, 2, 5]
///
/// - Parameter transform: A closure that accepts an element of this
/// sequence as its argument and returns an optional value.
/// - Returns: An array of the non-`nil` results of calling `transform`
/// with each element of the sequence.
///
/// - Complexity: O(*m* + *n*), where *m* is the length of this sequence
/// and *n* is the length of the result.
public func flatMap(_ transform: @noescape Element throws -> ElementOfResult?) rethrows -> [ElementOfResult]
文档里面写的很清楚,flatMap会滤掉nil的结果,换句话说他返回的是[T],而不是[T?]
let possibleNumbers = ["1", "2", "three", "///4///", "5"]
let mapped: [Int?] = numbers.map{str in Int(str)}
// [1, 2, nil, nil, 5]
let flatMapped: [Int] = numbers.flatMap{str in Int(str)}
// [1, 2, 5]
而且,FlatMap会对结果进行自动的flatten操作,也就是说flatMap(transform)其实相当于map(transform).flatten()
let numbers = [1, 2, 3, 4]
let mapped = numbers.map { $0 * 2 }
// [[1], [2, 2], [3, 3, 3], [4, 4, 4, 4]]
let flatMapped = numbers.flatMap { $0.map{ $0 * 2 } }
// [1, 2, 2, 3, 3, 3, 4, 4, 4, 4]
// 或者可以换一种易于理解的写法
let flatMappedAnother = numbers.flatMap { array in
array.map { element in
element * 2
}
}
// [1, 2, 2, 3, 3, 3, 4, 4, 4, 4]
Filter
在开发过程中过滤数据是再常见不过的操作了,比如从订单中筛选出金额大于xxx的订单来,如果使用for的方法,代码或许或是这样的:
let orders = [10, 30 ,45, 99]
// 筛选出大于30的结果
var result = [Int]()
for payment in orders {
if payment > 30 {
result.append(payment)
}
}
print(result)
// [45, 99]
使用filter后的代码:
let filterOrder = orders.filter{ $0 > 30}
print(filterOrder)
// [45, 99]
非常容易理解,那么我们再看看API文档是如何描述的:
/// Returns an array containing, in order, the elements of the sequence
/// that satisfy the given predicate.
///
/// In this example, `filter` is used to include only names shorter than
/// five characters.
///
/// let cast = ["Vivien", "Marlon", "Kim", "Karl"]
/// let shortNames = cast.filter { $0.characters.count < 5 }
/// print(shortNames)
/// // Prints "["Kim", "Karl"]"
///
/// - Parameter includeElement: A closure that takes an element of the
/// sequence as its argument and returns a Boolean value indicating
/// whether the element should be included in the returned array.
/// - Returns: An array of the elements that `includeElement` allowed.
public func filter(_ includeElement: @noescape Element throws -> Bool) rethrows -> [Element]
再好理解不过了,传入对应的转换条件就可以了,返回的是转换后的新元素数组
Reduce
reduce其实是对map,flatmap,filter的扩展,他可以实现其他三个能实现的任何功能,下面我们使用reduce来实现上面的几个类似的功能看看reduce的强大
// 对numbers数组中的数进行累加
let result2 = numbers.reduce([Int]()){a, x in
var result = a
result.append(x + 2)
return result
}
// 对nil进行过滤
let numbsers: [Int?] = [1, 2, nil, 4]
// reduce
numbsers.reduce([Int]()) { (a, x) in
var result = a
if let temp = x {
result.append(temp)
}
return result
}
// 下面filter的操作大致相同
那么我们现在可以大致猜测以下,reduce内部到底做了什么?我们先使用最笨的方法,看看reduce里面做了什么?
print([1, 2, 3, 4].reduce([Int]()){a, x in
var result = a
print("x -> \(x)")
print("a -> \(a)")
result.append(x + 2)
return result
})
// Result
x -> 1
a -> []
x -> 2
a -> [3]
x -> 3
a -> [3, 4]
x -> 4
a -> [3, 4, 5]
[3, 4, 5, 6]
从结果来看,a会拼接每次得到的结果,也就是每次结束的时候result又被赋值给了a,而x是每次遍历时数组中对应的元素。那么我可以猜测reduce的实现可能是这样的:
func reduce(arr: [A], initialValue: R, combine: (R, A) -> R) -> R {
var result = initialValue
for i in arr {
result = combine(result, i) // result又被赋值回去了
}
return result
}
接下来,我们可以使用reduce来重新实现map和filter函数
func mapReduceVersion(rls: [T], f: T -> U) -> [U] {
return reduce(rls, []){result, x in result + [f(x)]}
}
func filterReduceVersion(rls: [T], f: T -> Bool) -> [T] {
return reduce(rls, []) {result, x in
return f(x) ? result + [x] : result
}
}
这几个函数的使用是次要的,主要是要理解其中的原理,多多去探索,坑太多了...
如有错误之处,欢迎指出讨论
参考资料:
objc.io
swift.gg
生命不息,折腾不止...
I'm not a real coder,but i love it so much!