《Swift进阶》内建集合类型 数组

数组和可变性

在swift中,使用let定义的数组为不可变的数组,使用var定义的数组为可变数组。在不可变的数组中使用append这样的方法,会得到一个编译错误。

swift的Array数组是struct值类型,当复制到新变量的时候里面的值会被复制,也就是说改变其中一个变量,另外一个变量不会受到影响。

在Foundation框架中的NSArray和NSMutableArray是引用类型的class,因为引用类型的特性,使用let并不能保证这个数组不会被改变。

如果使用Foundation框架中的数组,为了安全,赋值最好使用copy方法进行。

数组和可选值

swift的数组提供了isEmpty或是count等常用的属性方法。数组允许直接使用下标直接访问其中的元素,但是在获取之前一定要保证索引值没有超出范围(越界),否则程序会崩溃。

记录几个常用方法:

  • 数组遍历: for x in array
  • 遍历除了第一个以外的数组的其余部分: for x in array.dropFirst()
  • 遍历数组除了最后5个元素以外的部分: for x in array.dropLast(5)
  • 同时获取数组的下标和元素:for (index,element) in collection.enumerated()
  • 获取指定元素的位置:if let idx = array.index{someMatchingLogic($0)}
  • 改变数组里面所有的元素: array.map{someTransformation($0)}
  • 筛选符合条件的元素:array.filter{someCriteria($0)}

在Swift3 中传统的C风格遍历方法已经被移除了。

数组没有可选值选项,因为无效的下标操作可能会导致可控的崩溃。不过在内存安全上是安全的,因为在标准库中集合会执行边界检查,禁止越界索引对内存的操作。(简单地说就是,越界下标操作不会出现内存的问题(比如访问到不属于它的地址),因为被禁止了。)

不过数组的firstlast属性是可选值,因为可能是一个空数组。

数组变形

Map

在以前想操作数组里面所有的元素,需要在for里面进行遍历操作,在Swift中可以使用map方法,这个方法来自函数式编程的世界。

传统写法:

var squared : [Int] = []
let fibs = [0,1,2,3,4,5]
for fib in fibs {
squared.append(fib * fib)
}
print(squared) // [0, 1, 4, 9, 16, 25]

函数式编程:

let fibs = [0,1,2,3,4,5]
let squares = fibs.map { fib in fib * fib }
print(squared) // [0, 1, 4, 9, 16, 25]

使用map的好处,它很短,长度短意味着发生错误的几率也低,比以前的代码更清晰,一旦习惯map之后,只要见到map就会立即反应到这是一个对数组的每个元素进行遍历并返回的逻辑。

另外squares是map得到的结果,所以不需要将squares设置成var。

函数将行为参数化(方法作为参数)

map可以将模板代码分离出来,这些模板代码并不会随着每次调用发生变动,变动的是那些功能代码,也就是如何改变每个元素的逻辑代码(比如说要将数组的每个元素做什么处理)。map函数通过接受调用者提供的逻辑函数作为参数来实现。

以下是一些常用的的函数:

  • map 和 flatMap — 如何对元素进行变换,flatMap和map的区别是,flatMap会进行join操作并且没有可选值。
  • filter — 元素是否应该包含在结果中,通常在做筛选的时候很有用。
  • reduce — 如何将元素合并到一个总值中。
  • sequence — 序列中的下一个元素应该是什么?
  • forEach — 遍历,对于每一个元素应该做什么(和foreach用途一样)。
  • sort,lexicographicCompare,partition — 两个元素应该怎么样排序。(数组大到小 小到大排序等等可以使用这个)
  • index,first 和 contains 元素是否符合某个条件。
  • min 和 max — 两个元素中最小/最大值是哪个。
  • elementsEqual 和 starts — 两个元素是否相等。
  • split — 这个元素是否是一个分隔符。

这些函数的目的都是为了拜托哦代码中杂乱无用的部分,比如说创建新数组,对数据进行for操作等。这些杂乱的代码都被一个单独的单词替代了,我们就可以把重点放在如何表达真正重要的逻辑代码。

这些函数有一些拥有默认行为,除非做出指定。比如sort默认会把可以作为比较的元素按照升序排列等。

下面是我随便写的一个数组count计算器:


// @escaping : 逃逸闭包关键字 // 这个闭包在函数结束前调用 func count2(patt:@escaping (Int)->Bool) -> (([Int]) -> Int){ var count = 0 return { array in array.forEach({ (element) in if patt(element) { count = count + 1 } }) return count } } let arr1 = [1,2,3,4,5,6,7,8,9,10] let countFunc = count2 { return $0 % 2 == 0 } let count = countFunc(arr1) // 5

Fliter

一个常见的操作是检查一个数组,然后将这个数组中符合一定条件的元素过滤出来并用它们创建一个新的数组。对数组进行循环并根据条件过滤其中元素的模式可以用数组的filter方法表示。

如返回一个数组中的偶数函数

arr.filter {$0 % 2 == 0}

通过配合map和filter,可以轻易完成很多数组操作,而且不需要引入中间数组。比如寻找100以内同事满足是偶数并且是其他数字的平方数,我们可以对0..<10进行map来得到所有平方数,然后再用filter过滤出其中的偶数。

(0..<10).map{$0 * $0}.filter{%0 % 2 == 0} // [4,16,36,64] ``` 另外因为filter会创建一个创新的数组,并且对数组里面的每一个元素都进行操作,所以最好不要写以下的代码: ``` bigArray.filter{ **各种逻辑代码** }.count &gt; 0

建议使用 (demo代码写的比较随意,最好是extension Sequence):

func all(arr:[Int],predicate:(Int)-&gt;Bool)-&gt;Bool{
return !arr.contains{
!predicate($0)
}
}
let arr2 = [2,4,6,8]
let result = all(arr: arr2) { $0 % 2 == 0 }
print(result) // true

Reduce

map和filter都作用在一个数组上并产生一个新的经过修改过数组。不过有的时候想把所有元素合并成一个新的值。比如累加
,在以前的传统写法是这样的:

var total = 0
for num in fibs {
total = total + num
}

reduce 方法对应这种模式,它把一个初始值(在这里是0)以及中间值(total)与序列中的元素(num)进行合并的函数进行了抽象。使用reduce,我们可以将上面的代码写成这样:

let sub = fibs.reduce(0){
total,num in total + num
}

算数符也是函数,所以我们也可以写成这样:

fibs.reduce(0,+)

另外reduce的输出值类型可以和输入的类型不同

fibs.reduce(""){str,num in str + "\(num)"}

flatMap

有的时候我们需要将一个多维数组进行合并操作(转换成一维数组,展平,joined),举例:有一系列的文件路径,在不同的数组里面,想要提取到单一的数组当中。另外flatMap结果中不会有可选值,不需要解包,也没有nil。

使用 forEach 进行遍历

forEach函数和map函数非常类似,传入函数对序列中的每个元素执行一次。和map不同的是,forEach不需要返回值。

在forEach中,使用return并不能返回到外部函数,它仅仅只是返回到闭包的本身之外。

(1..<10).forEach{ number in print(number) if number > 2 {return}
}

在这段代码中,数字将会被全部被打印出。

在实际编码中,如果不需要使用break,return等跳出的循环遍历,建议使用forEach。

数组切片

除了通过单独的下标来访问数组中的元素(如arr[0]),我们还可以通过下标来获取某个范围的元素。比如,我们想获取数组中除了首个元素的其他元素,可以这样做:

let slice = fibs[1..<fibs.endIndex]
slice // [1,1,2,3,5]
type(of:slick) // ArraySlice

它返回的是数组的一个切片(slice),其中包含了原数组中从第二个元素到最后一个元素的数据。结果类型是ArraySlice,而不是Array。切片类型只是一种数组的表示方式,它背后的数据仍然是以前的数组,ArraySlice具有的方法和Array上定义的方法是一致的,因此可以当做数组来使用,如果需要转换成数组的话,可以将切片传递给Array的构建方法。

Array(fibs[1..<fibs.endIndex])

Zmsky

http://xloli.net/?page_id=11

发表评论