集合类型模块分四篇笔记来学习:
- 第一篇:
- 数组和可变性
- 数组的变形
- 第二篇:
- 字典和集合
- 集合协议
- 第三篇:
- 集合
- 第四篇:
- 索引
本篇开始学习第一篇,Go!
数组和可变性
Swift 数组具有值语义,它们从不会在不同的地方被共享使用。当你创建一个新的数组变量并且把一个已经存在的数组赋值给它的时候,这个数组会被复制。举个例子,在下面的代码中,x 将不会被更改:
var x = [1,2,3]
var y = x
y.append(4)
代码中var y = x 复制了x,之后对y进行修改并不会影响x,x的值仍然是[1,2,3]
对比一下NSArray在不可变性上的处理方法。NSArray中没有更改的方法,想要更改一个数组,需要使用NSMutableArray。但是,就算使用了不可变的NSArray,她的引用特性并不能保证这个数组不会被改变。如下代码示例:
let a = NSMutableArray(array:[1,2,3])
let b : NSArray = a
a.insertObject(4,atIndex:3)
此时b的值仍然被修改了!想要避免这种情况,就需要在赋值b时,先对a进行复制:
let a = NSMutableArray(array:[1,2,3])
let b = a.copy() as! NSArray
a.insertObject(4,atIndex:3)
此时b还是[1,2,3]。通过赋值时复制可以避免,但是当把数组在方法和函数之间来回传递的时候,事情就会边的麻烦起来,因为如果每次都进行复制,会造成很多不必要的浪费。
在swift中,相较于NSArray和MutableArray两种类型,数组只用统一的一种类型Array。使用let定义为不可变,var定义为可变。Array进行赋值时采用的是“写时复制”的技术,它保证只有在必要的时候才对数据进行复制。在上面的代码中,在调用y.append之前,x和y都将共享内部的存储。
数组变形
Map
对数组中的每个值执行转换操作是一个很常见的任务。之前常用的方法是声明一个可变的数组,然后循环遍历要操作的数组,取出每个元素进行处理然后添加到声明的可变数组上,如下:
var squared: [Int] = []
for fib in fibs {
squared.append(fib * fib)
}
而如果使用函数编程世界的map方法的话,将是如下这样:
let squared = fibs.map { fib in fib * fib }
这行代码有三个优势:1.代码更短,更清晰。2.squared 将由 map 的结果得到,我们不会再改变它的值,所以也就不再需要用 var 来进行声明了,我们可以将其声明为 let。另外,由于数组元素的类型可以从传递给 map 的函数中推断出来,我们也不再需要为 squared 显式地指明类型了。3.创造 map 函数并不难,你只需要把 for 循环中的代码模板部分用一个泛型函数封装起来就可以了。
以下代码是map的可能实现方式(实际上他是SequenceType的一个扩展,之后会学习到):
extension Array {
func map<U>(transform: Element->U) -> [U] {
var result: [U] = []
result.reserveCapacity(self.count)
for x in self {
result.append(transform(x))
}
return result
}
}
使用函数将行为参数化
仔细考虑map的实现方法时,就可以发现map将模板代码分离出来,这些模板代码并不会随着每次调用而改动,发生变动的只是哪些功能代码,map函数通过接受调用者所提供的变换函数作为参数来做到这一点。标准库中还有其他的将行为参数化的设计模式,如下:
- map 和 flatMap — 如何对元素进行变换
- filter — 元素是否应该被包含在结果中
- reduce — 如何将元素合并到一个总和的值中
- sort 和 lexicographicCompare — 两个元素应该以怎样的顺序进行排列
- indexOf 和 contains — 元素是否符合某个条件
- minElement 和 maxElement — 两个元素中的最小/最大值是哪个
- elementsEqual 和 startsWith — 两个元素是否相等
- split — 这个元素是否是一个分割符
还有很多其他的类似的很有用的函数,并不存在于标准库中。实际工作中,对于操作数组当发现写了很多次同样模式的代码,就要考虑对SequenceType添加一个小扩展,如下面这段代码查找数组中,是否有符合条件的元素:
let someArray:[SomeObject] = []
...
var object:SomeObject?
for oneObject in someArray where oneObject.passesTest(){
object = oneObject
break
}
扩展代码如下:
extension SequenceType{
func findElement(match:Generator.Element->Bool)->Generator.Element?{
for element in self where match(element){
return element
}
return nil
}
}
let object = someArray.findElement{$0.passesTest}
结合guard效果更好:
guard let object = someSequence.findElement({ $0.passesTest() })else { return }
Filter
对数组进行循环并且根据条件过滤其中元素的模式可以用数组的 filter 方法表示
fibs.filter { num in num % 2 == 0 }
为了少写一些代码,可以使用swift内建的用来待变参数的简写$0
fibs.filter { $0 % 2 == 0 }
Filter的实现和map相似:
extension Array {
func filter(includeElement: Element -> Bool) -> [Element] {
var result: [Element] = []
for x in self where includeElement(x) {
result.append(x)
}
return result
}
}
注意以下错误代码:
bigarray.filter { someCondition }.count > 0
filter会创建一个全新的数组,以上代码仅仅是为了检查是否有至少有一个元素满足条件,这种情况下使用contains更为合适:
bigarray.contains { someCondition }
这种做法会比原来快得多,主要因为两个方面:它不会去为了计数而创建一整个全新的数组,并且一旦匹配了第一个元素,它就将提前退出。一般来说,你只应该在需要所有结果时才去选择使用 filter。最后理解一下以下代码的意义:
extension SequenceType {
public func allMatch(predicate: Generator.Element -> Bool) -> Bool {
// 对于一个条件,如果没有元素不满足它的话,那意味着所有元素都满足它:
return !self.contains { !predicate($0) }
}
}
Reduce
map 和 filter 都作用在一个数组上,并产生另一个新的、经过修改的数组。不过有时候,你可能会想把元素合并为一个新的值。比如,要是我们想将元素的值全部加起来
let total = fibs.reduce(0) { total, num in total + num }
或者
fibs.reduce(0, combine: +)
reduce 的实现是这样的:
extension Array {
func reduce<U>(initial: U, combine: (U, Element) -> U) -> U {
var result = initial
for x in self {
result = combine(result,x)
}
return result
}
}
flatMap
有时候我们会想要对一个数组用一个函数进行 map,但是这个函数返回的是另一个数组,而不是单独的元素。这时候我们需要使用flatMap来将通过map获取到的数组的数组进行展平,flapMap的实现方式如下:
extension Array {
func flatMap<U>(transform: Element -> [U]) -> [U] {
var result: [U] = []
for x in self {
result.appendContentsOf(transform(x))
}
return result
}
}
flatMap 的另一个常见使用情景是将不同数组里的元素进行合并。为了得到两个数组中元素的所有配对组合,我们可以对其中一个数组进行 flatMap,然后对另一个进行 map 操作:
let suits = ["B","C","Z","W"]
let ranks = ["J","Q","K","A"]
let allCombinations = suits.flatMap { suit in
ranks.map { rank in
(suit, rank)
}
}
使用 forEach 进行迭代
在数组上还有一个叫做 forEach 的函数 (它被定义在 SequenceType 上,而 Array 实现了这个协议)。它和 for 循环的作为非常类似,不过它们之间有些细微的不同。技术上来说,我们可以不暇思索地将一个 for 循环替换为 forEach:
for element in [1,2,3] {
print(element)
}
[1,2,3].forEach { element in
print(element)
}
如果你想要对集合中的每个元素都调用一个函数的话,forEach 就非常有用。
因为 forEach 的实现使用了闭包,所以当你在重写包含有 return 的 for 循环时,可能遇到一些意想不到的情况。如下:
(1..<10).forEach { number in
print(number)
if number > 2 { return }
}
这段代码会将输入数组中的所有数字都打印出来。return 并没有停止循环,它做的仅仅是从闭包中返回。在某些情况下,比如第一个例子,forEach 可能会更好。不过,因为 return 在其中的行为不太明确,我们建议大多数情况下不要用 forEach。这种时候,使用常规的 for 循环可能会更好。
数组类型
切片
除了通过单独的下标来访问数组中的元素 (比如 fibs[0]),我们还可以通过下标来获取某个范围中的元素。比如,想要得到数组中除了首个元素的其他元素,我们可以这么做:
fibs[1..<fibs.endIndex]
它将返回数组的一个切片 (slice),其中包含了原数组中从第二个元素到最后一个元素的数据。得到的结果的类型是一个 ArraySlice,而不是 Array。切片类型只是数组的一种表示方式,它背后的数据仍然是原来的数组,只不过是用切片的方式来进行表示。这意味着原来的数组并不需要被复制。ArraySlice 具有的方法和 Array 上定义的方法是一致的,因此你可以把它们当做数组来进行处理。如果你需要将切片转换为数组的话,你可以通过将切片传递给 Array 的构建方法来完成:
Array(fibs[1..<fibs.endIndex])
桥接
Swift 数组可以桥接到 Objective-C 中。桥接时的转换会在两个方向发生:Swift的数组大多数时候可以被转换为NSArray,而 NSArray 则一定能被转换为 Swift 数组。
想要从 Swift 数组创建一个 NSArray,前提条件是其中的元素都必须能够转换为 AnyObject。Swift 对象都是可以转换为 AnyObject 的,除此之外,有一部分结构体类型也可以被转换为 AnyObject。比如说 Int 和 Bool 都可以被自动转换为 NSNumber。但其他有一些类型不行,比如 CGPoint 原本应该可以转换为 NSValue,但是现在不行。不过好消息是,编译器会在它不知道如何进行桥接的时候向你求助。
网友评论