map flatMap compactMap filter reduce sort
map(转换)
map(sequence, transformClosure): 如果transformClosure适用于所给序列中所有的元素,则返回一个新序列。
它接受一个闭包 transformClosure 作为参数,作用于数组中的每个元素,闭包返回一个变换后的元素,接着将所有这些变换后的元素组成一个新的数组。
// 计算字符串的长度
let stringArray = ["Objective-C", "Swift", "HTML", "CSS", "JavaScript"]
let counts = stringArray.map($0.count)
- 适用于任何数据类型,能够返回不同与最初的类型,不能过滤掉不符合规则的元素
// 转化为 [Int?] 因为内部元素可能不符合转化规则
let strings = ["1", "2", "fish"]
let a = strings.map {Int($0)}
print(a)
//[Optional(1), Optional(2), nil]
compactMap(执行转换,解包所有可选选项并丢弃nil值)
与 map 作对比,转化成了[Int],并且过滤掉解包后为 nil 的值
// 转化为 [Int?] 因为内部元素可能不符合转化规则
let strings = ["1", "2", "fish"]
let a = strings.map {Int($0)}
let b = strings.compactMap {Int($0)}
print(a)
print(b)
// [Optional(1), Optional(2), nil]
// [1,2]
使用场景:
// 读出子视图中的 UIImageView
let imgViews = view.subviews.compactMap { $0 as? UIImageView }
// 读出有效的 URL
let urls = urlStrings.compactMap { URL(string: $0) }
flatMap(降低数组维度,降低可选项维度,过滤nil值)
思考下面的代码:
// name 是 String? 类型
let name: String? = nil
// Int($0) 会将字符串转换为可选整数,因为字符串可能是非数字形式,例如“ Fish”。
// greeting 将是一个 Int?? 类型
let greeting = name.map { Int($0) }
广义上讲,任何时候当你看到可选的可选内容时,某个地方就已经出现了问题,你应该重新考虑。
可选的可选意味着:
- 外部可选项可能存在,而内部可选项可能存在.
- 可能存在外部可选项,但内部可选项可能为nil。
- 外部可选项可能为nil,这意味着没有内部可选项。
可选的可选参数使用起来非常混乱,但这就是 flatMap() 出现的地方:它不仅执行转换,随后将返回的内容展平,因此“可选的可选参数”变为“可选的”。
它会将双重可选值变为单个可选值。最终,我们不在乎外部可选或内部可选是否存在,仅在乎其中是否存在值,这就是为什么flatMap()如此有用的原因。
let number: String? = getUser(id: 97)
// 使用 flatMap 展平了一次,result 为 Int?
let result = number.flatMap { Int($0) }
与 map 的区别:
- flatMap 会降低数组维度,过滤nil值,
但是每次操作只执行一种功能,优先执行过滤 nil的操作。
- 在没有明确指定原数组为
:[Any]
的情况下,会将 [String] 处理为 [String.Element],里面的元素由 String 降为 Character - 嵌套情况见下
let arr = [[[1,1,1],2],[3,4], nil, nil]
// 优先处理nil值
let arr1 = arr.flatMap{ $0 }
let arr2 = arr.flatMap{ $0 }.flatMap{ $0 }
print(arr1, arr2, separator: "\n")
// [[[1, 1, 1], 2], [3, 4]]
// [[1, 1, 1], 2, 3, 4]
使用效果对比
总结:
- map 和 compactMap 返回的元素不改变原先的类型
- 在嵌套 map 的情况下,flatMap 会将 String 降维至 Character
- 在不是嵌套 map 的情况下
- 若原序列是 String 序列,不指定类型或指定为[String]:flatMap 将元素处理成 Character
- 指定[Any]类型,flatMap 与 map、compactMap 一致,不改变原先类型。
- flatMap 仅对 String 降为
let level = ["A", "B", "C"]
let map = level.map{$0}
let flatMap = level.flatMap{$0}
let compactMap = level.compactMap{$0}
let level = ["A", "B", "C"]
let num = [1, 2]
let result = level.??? { (level) in
num.??? { (num) in
return level + String(num)
}
}
作用于可选对象
map()方法也存在于可选对象上:如果一个可选类型有值,map会获取这个值,经过map的闭包处理变为另外一个值,如果这个可选类型的值为nil,那么不会执行map闭包,而是直接返回nil。
// 如果name包含字符串,则map()会将解包,将其转换为“ Hi, name包含的字符串”,然后将整个拼接后的字符串放入一个可选对象中并返回以存储在greeting中。
// 如果name不包含字符串为nil,map()将直接返回nil给greeting。
let name: String? = valueOrNil(id: 97)
let greeting = name.map { "Hi, \($0)" }
print(greeting ?? "Unknown user")
类似的 flatMap() 同样可以作用于可选对象,区别是map闭包不能return nil,而flatmap可以
var value: Double? = 10
var newValue: Double? = value.flatMap { v in
if v < 5.0 {
return nil
}
return v / 5.0
}
// newValue is now Optional(2)
newValue = newValue.flatMap { v in
if v < 5.0 {
return nil
}
return v / 5.0
}
// now it's nil
使用场景:替代三目运算符
一般在使用可选对象时,必须分有值和nil值的情况,运用到三目运算符时肯定会使用强制解包,这样很不优雅。
使用 map 代替,如果可选值为 nil,则会直接返回 nil,不会进入后面的闭包。
// 时间操作
var date: NSDate? = ...
// var formatted: String? = date == nil ? nil : NSDateFormatter().stringFromDate(date!)
var formatted: String? = date.map(NSDateFormatter().stringFromDate)
// 获取String字面量的值
func ageToString(age: Int?) -> String {
//return age == nil ? "Unknown age" : "She is (age!) years old"
return age.map { "She is ($0) years old" } ?? "Unknown age"
}
// 本地化字符串
let label = UILabel()
func updateLabel(value: String?) {
label.text = value.map {
String.localizedStringWithFormat(NSLocalizedString("value %@", comment: ""), $0)
}
}
// 构造枚举
enum State: String {
case Default = ""
case Cancelled = "CANCELLED"
static func parseState(state: String?) -> State {
return state.flatMap(State.init) ?? .Default
}
}
// 在数组中找到某项
func find(identifier: String) -> Item? {
return items.indexOf({$0.identifier == identifier}).map({items[$0]})
}
// json字典构造对象
func createPerson(json: [String: AnyObject]) -> Person? {
return (json["person"] as? [String: AnyObject]).flatMap(Person.init)
}
filter(过滤)
可以对数组中的元素按照某种规则进行一次过滤
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9]
//筛选偶数值
let evens = numbers.filter{$0 % 2 == 0} //等同于
let even = numbers.filter{(num) -> Bool in
num % 2 == 0
}
//筛选奇数值
let odds = numbers.filter{$0 % 2 == 1} //等同于
let odd = numbers.filter{(num) -> Bool in
num % 2 == 1
}
reduce(归纳合并成一个元素)
Reduce 的基础思想是将一个序列转换为一个不同类型的数据,期间通过一个累加器(Accumulator)来持续记录递增状态。
reduce(initial, combineClosure): 从第一个初始值开始对其进行combineClosure操作,递归式地将序列中的元素合并为一个元素
-
combineClosure:规则闭包,要返回如何将元素合并,有两个参数
- $0 代表累加器,初值等于 initial
- $1 代表遍历数组得到的一个元素
var languages = ["Swift", "OC", "java"]
let r = languages.reduce("_", {$0 + $1}) //等同于
let r = languages.reduce("_") { (result, next) -> String in
print("result: \(result)")
print("next: \(next)")
return result + next
}
print(r)
/**
result: _
next: Swift
result: _Swift
next: OC
result: _SwiftOC
next: java
_SwiftOCjava
*/
print([10, 20, 5].reduce(1, { $0 * $1 }) == 1000) // true
// 极简形式 [10, 20, 5].reduce(1, *)
值的注意的问题
- 仅传入计算符号 "+" "*" 作为一个 combinator 函数是有效的
- 它仅仅是对 lhs(Left-hand side,等式左侧) 和 rhs(Right-hand side,等式右侧) 做计算处理,最后返回结果值
[0, 1, 2, 3, 4].reduce(0, +)
[1, 2, 3, 4].reduce(1, *)
- 反转数组:
[1, 2, 3, 4, 5].reduce([Int](), { [$1] + $0 })
// [element]:[1] result:[]
// [element]:[2] result:[1]
// [element]:[3] result:[2, 1]
// [element]:[4] result:[3, 2, 1]
// [element]:[5] result:[4, 3, 2, 1]
- 性能问题:
使用高阶函数之前多考虑实现方案,通常情况下,map 和 filter 所组成的链式结构会引入性能上的问题,因为它们需要多次遍历你的集合才能最终得到结果值,这种操作往往伴随性能损失。
// 初始序列(即 [0,1,2,3,4])被重复访问了三次之多
[0, 1, 2, 3, 4].map({ $0 + 3}).filter({ $0 % 2 == 0}).reduce(0, combine: +)
// 可用 reduce 完全替换实现,极大提高执行效率:
[0, 1, 2, 3, 4].reduce(0, { (ac, r) in
if (r + 3) % 2 == 0 {
return ac + r + 3
} else {
return ac
}
})
// for-loop 版本
var ux = 0
for i in 0...100000 {
if (i + 3) % 2 == 0 {
ux += (i + 3)
}
}
不过,在某些情况中,链式操作是优于 reduce 的。思考如下范例:
[0...100000].map({ $0 + 3}).reverse().prefix(3)
// 0.027 Seconds
[0...100000].reduce([], combine: { (var ac: [Int], r: Int) -> [Int] in
ac.insert(r + 3, atIndex: 0)
return ac
}).prefix(3)
// 2.927 Seconds
使用链式操作花费 0.027s,这与 reduce 操作的 2.927s 形成了鲜明的反差,从 reduce 的语义上来说,传入闭包的参数(如果可变的话,即 mutated),会对底层序列的每个元素都产生一份 copy 。在我们的案例中,这意味着 accumulator 参数 ac 将为 0...100000 范围内的每个元素都执行一次复制操作。Arrays, Linked Lists and Performance
因此,当我们试图使用 reduce 来替换掉一组操作时,请时刻保持清醒,问问自己:reduction 在问题中的情形下是否确实是最合适的方式。
更多范式
- min:返回列表中的最小项。[1,5,2,9,4].minElement() 方法更胜一筹。
// 初始值为 Int.max,传入闭包为 min:求两个数的最小值
// min 闭包传入两个参数:1. 初始值 2. 遍历列表时的当前元素
// 倘若当前元素小于初始值,初始值就会替换成当前元素
// 示意写法: initial = min(initial, elem)
[1, 5, 2, 9, 4].reduce(Int.max, min)
- unique:剔除列表中重复的元素。最好的解决方式是使用集合(Set)
[1, 2, 5, 1, 7].reduce([], { (a: [Int], b: Int) -> [Int] in
if a.contains(b) {
return a
} else {
return a + [b]
}
})
// prints: 1, 2, 5, 7
sort
let Arr = [13, 45, 27, 80, 22, 53]
// 完整
let sort1 = Arr.sorted { (a: Int, b: Int) -> Bool in
return a < b
}
// 简略
let sort2 = Arr.sorted { $0 < $1 }
// 极简
let sort3 = Arr.sorted(by: <)