Swift 集合类高阶函数

Swift的集合类型中,有许多十分便捷的函数。相比于Objective-C,这些高阶函数会引起你的极度舒适。因为在Swift的许多函数中引入了闭包元素,这就直接造就了它的灵活性,简直就是极致的便捷。

下面就来对Swift集合类中的这些高阶函数进行总结。

// 全文的基础数据
let numbers = [7, 6, 10, 9, 8, 1, 2, 3, 4, 5]

1.sort函数

对原集合进行给定条件排序。
无返回值,直接修改原集合,所以这个集合应该是可变类型的。

var sortArr = numbers
sortArr.sort { a, b in
    return a < b
}       
// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

另外,系统还定义了一个sort()函数,即对集合进行升序排序的函数。但这个函数并不是上面函数不传入缺省值的情况,而是另外一个函数。

var sortArr2 = numbers
sortArr2.sort()     
// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

2.sorted函数

sorted函数sort函数对应。
将集合进行给定条件排序,返回一个新的集合,不修改原集合。

let sortedArr = numbers.sorted { a, b in
    return a > b
}
// [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]

// sorted()函数
let sortedArr2 = numbers.sorted()
// [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]

// 闭包简写
let sortedArr3 = sortedArr2.sorted(by: >)
// [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]

闭包的省略写法

因为在高阶函数中大部分都使用了闭包,所以我认为有必要做一个铺垫,以更好地理解本文。清楚闭包简写的请跳过本段,直奔第3条

由于sort函数使用了闭包,所以自主定义的闭包可以简写为如下格式:

numbers.sort(by: >)     
// [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]

以上述方法为例,一个完整的闭包应该是这样的:

numbers.sorted { (a: Int, b: Int) -> Bool in
    return a > b
}

然后,可以省略闭包中的返回值。

numbers.sorted { (a: Int, b: Int) in
    return a > b
}

然后,再可以省略形参的类型,让编译器去自主推断。

numbers.sorted { a, b in
    return a > b
}

再然后,还可以让$0$1…来代替第一个,第二个形参,以此类推。

numbers.sorted { return $0 > $1 }

再然后,省略return。一般的,到这里也就足够简化了。毕竟在实际开发中我们需要使用闭包中的参数进行一些复杂的判断。

numbers.sorted { $0 > $1 }

如果你不需要复杂的判断,那么还可以写成下面这样,代表降序排序。

numbers.sorted(by: >)

3.map函数

按照闭包中的返回结果,将集合中对应元素进行替代,也就是映射函数。

// 数组数值转换为其各自平方
let mapArr = numbers.map { $0 * $0 }
// [49, 36, 100, 81, 64, 1, 4, 9, 16, 25]

可选类型的 map, flatMap函数

另外,不仅CollectionTypemapflatMap函数,在Optional类型中,也存在这两个函数。
它们的作用是对可选类型就行解包操作,若有值则进入闭包,并返回一个 Optional类型;若为nil,则直接返回当前可选类型的nil。

let num1: Int? = 3
let num2: Int? = nil

let numMap1 = num1.map {
    $0 * 2
}
numMap1              // 6
type(of: numMap1)    // Optional<Int>.Type

let numMap2 = num2.map {
    $0 == 0
}
numMap2              // nil
type(of: numMap2)    // Optional<Bool>.Type


let numFlatMap1 = num1.flatMap {
    $0 * $0
}
numFlatMap1              // 9
type(of: numFlatMap1)    // Optional<Int>.Type

let numFlatMap2 = num2.flatMap {
    $0 == 0
}
numFlatMap2              // nil
type(of: numFlatMap2)    // Optional<Bool>.Type

还有一种应用场景,就是解析可选类型的时候,mapflatMap函数会让你的代码更加优雅。
举个例子,当解析并判断可选类型的时候,你可能会经过一堆if或者guard判断,如下所示:

func loadURL(url: URL) {
    print(url.absoluteString)
}

let urlStr: String? = "https://github.com/sampson0115"
guard let siteStr = urlStr else {
    assert(false)
}
guard let url = URL(string: siteStr) else {
    assert(false)
}
loadURL(url: url)

如果使用mapflatMap函数的话,就会有十分优雅的感觉。

// 这行优雅的代码代替上面的代码
urlStr.flatMap(URL.init).map(loadURL)

但有一点需要注意,这里 map替换 flatMap会报错, 原因在于 flatMap闭包可以返回 nil, 而 map闭包不可以。就如下面的代码编译不会通过:

// compile error
// urlStr.map(URL.init).map(loadURL)

再举一个例子:

let date: Date? = Date()
let format = date.map(DateFormatter().string)

4.flatMap函数

也是一种映射函数,这个函数具有多重功能,所以也就造成了这个函数有一个历史问题,稍后会解释。
第一种情况,解析首层元素,若有nil则过滤,就不会降维

let optLatticeNumbers = [[1, Optional(2), 3], [3, nil, 5], nil]
// 解析首层元素, 若有nil则过滤, 就不会降维
let flatMapArr2 = optLatticeNumbers.flatMap { $0 }
// [[1, 2, 3], [3, nil, 5]]

第二种情况,解析首层元素,若没有nil,则会降维

let latticeNumbers = [[1, Optional(2), 3], [3, nil, 5]]
// 解析首层元素, 若没有nil, 则会降维
let flatMapArr = latticeNumbers.flatMap { $0 }
// [1, 2, 3, 3, nil, 5]

所以flatMap的功能就有两个了,一个功能是解析并过滤首层元素为nil的元素,一个功能是对多维集合进行降维。原因是,其实这是两个功能是两个函数,只是在调用时代码上没有区别。

flatMap和compactMap的关系

但从表面上看,flatMap函数违背了单一功能原则,将过滤nil降维两个功能于隐藏条件中进行判定。这也就是那个历史问题。
因此,为了将过滤nil降维两个功能于区分开,swift4.1开始,就只保留了降维的flatMap函数,并弃用了过滤nil的flatMap函数,又用开放的新函数compactMap来替代弃用的函数。
所以,当需要过滤nil的时候,请使用compactMap函数;当需要进行降维时,请使用flatMap函数。这也就是flatMapcompactMap之间的区别。

5.compactMap函数

Swift4.1开始开放的一种映射函数,会解析并过滤首层元素为nil的元素。

let compactMapArr = optLatticeNumbers.compactMap { $0 }
// [[1, 2, 3], [3, nil, 5]]
let compactMapArr2 = latticeNumbers.compactMap { $0 }
// [[1, 2, 3], [3, nil, 5]]

compactMap函数作为过滤nil的flatMap函数的替代函数。当集合中的元素为一个一维集合,他们之间的功能是没有差别的。

let flatNumbers = [1, Optional(2), 3, nil, Optional(5), nil]

let flatMapArr = latticeNumbers.flatMap { $0 }
// [1, 2, 3, 5]
let compactMapArr = optLatticeNumbers.compactMap { $0 }
// [1, 2, 3, 5]

6.filter函数

按照条件进行元素过滤。

let filterArr = numbers.filter { num in
    return num < 3 || num > 8
}
// [10, 9, 1, 2]

7.reduce函数

以指定参数为基础,按照条件进行拼接

let reduceNumber = numbers.reduce(100) { result, num in
    return result + num
}
// 155

let reduceString = ["C", "O", "D", "E"].reduce("word: ") { result, num in
    return result + num
}
// "word: CODE"

8.prefix函数

正向取满足条件的元素,进行新集合创建。一旦出现不满足条件的元素,则跳出循环,不再执行。

let prefixArr = numbers.prefix { $0 < 10 }
// [7, 6]

prefix相关函数:
upTo: 正向取元素创建数组, 包含小于指定index的元素

let prefixUpToArr = numbers.prefix(upTo: 5)
// [7, 6, 10, 9, 8]

through: 正向取元素创建数组, 包含小于等于指定index的元素

let prefixThroughArr = numbers.prefix(through: 2)
// [7, 6, 10]

maxLength: 正向取元素创建数组, 包含指定的元素个数

let prefixMaxLengthArr = numbers.prefix(6)
// [7, 6, 10, 9, 8, 1]

9.drop函数

prefix函数对应。正向跳过满足条件的元素,进行新集合创建。一旦出现不满足条件的元素,则跳出循环,不再执行。

let dropArr = numbers.drop { $0 < 10 }
// [10, 9, 8, 1, 2, 3, 4, 5]

drop相关函数:
dropFirst: 正向跳过元素创建数组, 跳过指定元素个数, 缺省值为1

let dropFirstArr = numbers.dropFirst(3)
// [7, 6, 10, 9, 8]

dropLast: 返向跳过元素创建数组, 跳过指定元素个数, 缺省值为1

let dropLastArr = numbers.dropLast(5)
// [7, 6, 10, 9, 8]

10.first函数

正向找出第一个满足条件的元素

let first = numbers.first { $0 < 7 }
// 6

11.last函数

first函数对应。反向找出第一个满足条件的元素。

let last = numbers.last { $0 > 5 }
// 8

12.firstIndex函数

正向找出第一个满足条件的元素下标。

let firstIndex = numbers.firstIndex { $0 < 7 }
// 1

13.lastIndex函数

反向找出第一个满足条件的元素下标。

let lastIndex = numbers.lastIndex { $0 > 5 }
// 4

14.partition函数

按照条件进行重新排序,不满足条件的元素在集合前半部分,满足条件的元素后半部分,但不是完整的升序或者降序排列。
返回值为排序完成后集合中第一个满足条件的元素下标。

var partitionNumbers = [20, 50, 30, 10, 40, 20, 60]
let pIndex = partitionNumbers.partition { $0 > 30 }
// partitionNumbers = [20, 20, 30, 10, 40, 50, 60]
// pIndex = 4

15.min函数

按条件排序后取最小元素。

let min = numbers.min { $0 % 5 < $1 % 5 }
// 10

min()函数,自然升序取最小。

let minDefault = numbers.min()
// 1

16.max函数

按条件排序后取最大元素。

let maxDictionary = ["aKey": 33, "bKey": 66, "cKey": 99]
let max = maxDictionary.max { $0.value < $1.value }
// (key "cKey", value 99)

max()函数,自然升序取最大。

let maxDefault = numbers.max()
// 10

17.removeAll函数

移除原集合中所有满足条件的元素。
无返回值,直接修改原集合,所以这个集合应该是可变类型的。

var removeArr = numbers
removeArr.removeAll { $0 > 6 }
// [6, 1, 2, 3, 4, 5]

18.集合遍历

forEach函数:

numbers.forEach { num in
    print(num)
}

for-in函数:

for num in numbers where num < 5 {
    print(num)
}

enumerated()函数配合使用:

for (index, num) in numbers.enumerated() {
    print("\(index)-\(num)")
}

19.shuffled函数

shuffled函数,打乱集合中元素的的顺序。

let ascendingNumbers = 0...9
let shuffledArr = ascendingNumbers.shuffled()
// [3, 9, 2, 6, 4, 5, 0, 1, 7, 8]

20.contains函数

contains函数,判断集合中是否包含某元素。

let containsBool = numbers.contains(8)
let containsBool1 = numbers.contains(11)
// true
// false

21.split和joined函数

split函数,字符串的函数,按条件分割字符串,为子字符串创建集合。与Objective-C中的componentsSeparatedByString:方法类似。

let line = "123Hi!123I'm123a123coder.123"
let splitArr = line.split { $0.isNumber }
// ["Hi!", "I'm", "a", "coder."]

// 也可指定字符
let splitArr2 = line.split(separator: "1")
// ["23Hi!", "23I'm", "23a", "23coder.", "23"]

joined函数,数组元素连接指定字符拼接成一个字符串。与Objective-C中的componentsJoinedByString:方法类似。

let joined = splitArr.joined(separator: "_")
// "Hi!_I'm_a_coder."

// 也可以只传入字符
let joined2 = splitArr2.joined(separator: "#")
// "23Hi!#23I'm#23a#23coder.#23"

22.zip函数

将两个数组合并为一个元组组成的数组。

let titles = ["aaa", "bbb", "ccc"]
let numbers = [111, 222, 333]
let zipA = zip(titles, numbers)
for (title, num) in zipA {
    print("\(title)-\(num)")
}

打印结果:

aaa-111
bbb-222
ccc-333
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容