75142913在线留言
深入学习swift数组(Array)看这一篇就够了_IOS开发_网络人

深入学习swift数组(Array)看这一篇就够了

Kwok 发表于:2021-10-01 11:01:48 点击:2 评论: 0

Array在开发中使用场景非常多,是swift内置类型中开发应用程序最常用的数据类型之一,我们应该熟悉并理解数组自带的一些功能。Swift 数组使用有序列表存储同一类型的多个值。相同的值可以多次出现在一个数组的不同位置中。可以有顺序的随机访问集合。

一、了解Array

在cs193p的课程里http://www.neter8.com/ios/147.html 讲泛型的时候说到过Array的底层代码:

//Swift 数组会通过传入的泛型Element强制检测元素的类型
@frozen struct Array<Element>{ 
    ...
    func append(_ element:Element){...}//如果Element类型不同则会报错
}
//通过@frozen限制可以对此类型进行更改的种类并仅在库演化模式下编译时才允许使用此属性。
//@frozen 只用于修饰结构(struct)或枚举(enum)

所以Swift 数组应该遵循像Array这样的形式,其中Element是这个数组中唯一允许存在的数据类型,在初始化始传入或者自动推导数据类型。

上面代码里可以看到数组传入的是一个泛型,所以数组可以存储任何类型的元素(从整数到字符串再到类)。

二、数组基本使用

Swift 可以轻松地使用数组字面量在代码中创建数组(只需用方括号将逗号分隔的值列表括起来即可)。没有任何其他信息,Swift 创建一个包含指定值的数组,自动推断数组的Element类型。例如:

let myNumbers1:Array = Array([1, 2, 4, 8, 16, 32, 64, 128])//非简写的数组定义
let myNumbers2:Array = [1, 3, 5, 7, 9, 11, 13, 15]//定义数组并指定类型
let myNumbers3:[Int] = [2, 4, 6, 7, 8, 10, 12, 14]//简写并指定数据类型
let myNumbers4 = [2, 4, 6, 7, 8, 10, 12, 14]//自动推导数组类型

let nemas = ["张三", "李四", "王五"]//自动推导"String"类型

1、创建空数组

日常开发中通常我们都是先创建一个空数组,然后使用内置的方法将数组写入到数组里,创建空数组可以使用下面的几种方式:

var emptyDoubles: [Double] = [] //使用缩写让swift自动推导的方式优先
var emptyInt = [Int]() //效果同上
var emptyFloats: Array = Array()//完整的写法
var emptyStrings = Array()//另一种完整的写法
var emptyStringsPlus:Array = Array()//很累且没有必要的写法

注意:上面的空数组都是使用的var定义,如果我们将var修改为let将创建一个只读的数组,无法调用写入数组的方法(.append)或者修改数组里的值。

2、快速创建带默认数据的数组

//Array(repeating:InitialValue,count: NumbeOfElements)初始化器
//快速创建数量为count = 0 ,初始值为repeating = 20 的数组
var digitCounts = Array(repeating: 0, count:20)

上面的效果如果手动写代码:

var digitCounts = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

3、修改数组元素值

上面我们初始 化了20个值为0的数组,现在我们将尝试修改数组中第二个元素的值:

digitCounts[1] = 2//将数组中第二个值0修改为2
digitCounts[10] = 11//将数组中第11个值0修改为11
digitCounts[3] += 3//将数组中第4个值0修改为 0 + 3

我们使用digitCounts[index] 的方式来访问数组元素,其中index 索引和其它编程语言一样从 0 开始,即索引 0 对应第一个元素,索引 1 对应第二个元素,以此类推。

4、访问/遍历数组

通常情况下我们使用for-in循环迭代数组的内容。可以对数组的所有元素执行操作。

for digit in digitCounts {
    print(digit)//打印值到控制台
}
//.enumerated返回一个由(n, x)对组成的序列,其中n表示从0开始的连续整数,x表示序列中的对应的元素值。
for (index, digit) in digitCounts.enumerated() {
    print("第(index)个元素的值:(digit)")
}

5、访问指定元素的值

和修改数组元素值一样,我们通过索引来访问指定元素的值:

print(digitCounts[1],digitCounts[10],separator: ", ")//打印:2 ,11
print(digitCounts[20])//报错:Index out of range

当通过下标访问单个数组元素的时候,超出来索引范围将会报错,所以我们需要使用安全的方式去读取数组元素的值。非空数组的第一个元素始终位于索引[0]处。可以使用从零到(但不包括)数组计数的任何整数为数组下标。使用负数或等于或大于的索引count会触发运行时错误。

6、数组区间

我们还可以使用一个区间数访问数数组元素:

print(digitCounts[1...4])//打印2~4的索引值元素

我们还可以使用另一种区间方式:

print(digitCounts[3..<digitCounts.endIndex])//从索引3到数组最后一个
//与直接digitCounts效果一样,indices是一个范围值:0..

二、数组使用进阶

除了上面的数组的基本使用方式外,swift还内置了很多实用的方法对数组操作。

1、检测数组是否为空

var Numbers = [Int]()//定义Int类型的空数组
//Numbers.append(1) //追加到数组
if Numbers.isEmpty {
    print("数组内容为空")//String也可以使用.isEmpty
} else {
    print("我找到了(Numbers.count)个数组元素.")
}
//.count的底层是对Collection遍历累加得到的值(类似上面的for-in)

注意:.isEmpty只能检测被定义为Collection集合(包括数组)的变量/常量。当需要检查Collection(集合)是否为空时请使用isEmpty属性,而不是检查count属性是否等于零。对于不符合RandomAccessCollection的集合,访问count属性将遍历该集合的元素(影响效率)。

2、访问数组第一个和最后一个元素。

使用first和last属性安全访问数组的第一个和最后一个元素的值(Optional)。如果数组为空,则这些属性为nil。

//安全读取Optional类型
if let firstElement = Numbers.first, let lastElement = Numbers.last {
    print(firstElement, lastElement, separator: ", ")
}
//空数组将打印nil, nil
print(Numbers.first, Numbers.last, separator: ", ")

3、获取数组开始/结束后的位置

print(a.startIndex) //非空数组中第一个元素的位置。
print(a.endIndex) //endIndex返回数组的“结束后”位置,并不是数组最后的索引值

 如果数组为空,则endIndex等于startIndex。在这里可能会有点疑问了,我们知道数组总是从0开始的,count获取到的值和endIndex一样。看下面的代码:

let a = ["a", "b", "c", "d", "e"]
a.startIndex  // 0
a.count       // 5
a.endIndex    // 5
//遇到下面的数组切片(ArraySlice)情况就有用了
let slice = a[1..<4]  // "b", "c", "d"
slice.startIndex  // 1
slice.count       // 3
slice.endIndex    // 4 

//访问slice的结果
print(slice.first)//Optional("b") 使用 slice[slice.startIndex]
print(slice.last)//Optional("d") 使用 slice[slice.endIndex - 1]
print(slice[0])//Fatal error: Index out of bounds

 当遇到使用let定义的ArraySlice数组时,由于常量数组不可变,所以索引值不会重新排序,我们就需要使用startIndex、endIndex安全的访问数组里的内容了。

4、增加数组元素.append

我们可以对数组追回一个元素,或者删除指定的某个元素,我们可以使用以下几种方式:

要将单个元素添加到数组的末尾,请使用append(_:)方法。

var students = ["张三", "李四", "王五"]
students.append("赵六")//在“王五”后面增加一个“赵六”
students += "赵七" //和上面一样在尾部追加

 通过将另一个数组或任何类型的序列传递给该方法,同时添加多个元素。append(contentsOf:)

var appArr = ["孙铁牛", "吴名字"]
//将另一个数组追到后面,也可以写成students.append(contentsOf: ["孙铁牛", "吴名字"])
students.append(contentsOf: appArr)

 这样可相当于2个数组合并成了一个数组,我们需要这2个数组的类型保持一致,我们还有另一个方式可以实现上面相同的功能:

students += appArr //使用“+”也可以完成在数组后面追加 students += ["孙铁牛", "吴名字"]

5、将元素插入到指定位置.insert

可以通过使用单个元素的方法和使用从另一个集合或数组文字插入多个元素在数组中间添加新元素。该索引处的元素和后面的索引被移回以腾出空间。insert(_:at:)insert(contentsOf:at:),使用方法与.append类型,只增加一个插入位置参数:

students.insert("刘二", at: 1)//在“张三”后面插入“刘二”

 插入数组到指定索引位置:

students.insert(contentsOf: ["郑大","光明"], at: 0)//在数组开始位置插入新的数组

 当然我们也可使用“+”来完成在数组的开始位置插入:

students = ["郑大","光明"] + students//效果和上面一样

 6、删除数组中的元素

从数组中删除元素,使用remove(at:)单个删除,removeSubrange(_:)范围删除,removeLast()删除最后一个元素。

a.单个删除

students.remove(at: 0)//删除数组中第一个元素
students.remove(at: -1) //超出数组范围将报错

b.删除数组指定范围

students.removeSubrange(1..<4)//删除第2、3、4个元素
students.removeSubrange(3..<students.count)//删除从第4个开始到最后的所有元素

 c.删除最后一个元素

students.removeFirst()//删除第一个元素
students.removeLast()//删除最后一个元素
//如果数组为空报错:Fatal error: Can't remove last element from an empty collection

 d.删除满足条件的所有元素

var site = "www.neter8.com 网络人技术网"
let vowels: Set = ["w", "人", "网"]
site.removeAll(where: { vowels.contains($0) })// 将vowels依次传入
print(site)//.neter8.com 络技术

 e.清空数组

var site = ["www.neter8.com","网络人技术网"]
//当keepingCapacity为true时从数组中删除所有元素但保留数组内存空间。
site.removeAll(keepingCapacity: true)
print(site.count)//0
print(site.capacity)//2
//参数默认为false,内存空间也将会被回收
site.removeAll()
print(site.capacity)//0

 g.移除并返回集合的最后一个元素。

如果集合不为空,则为集合的最后一个元素;否则,nil。调用此方法可能会使此集合的所有已保存索引无效。在使用任何可以更改其长度的操作更改集合后,不要依赖先前存储的索引值。

var site = ["www.neter8.com","网络人技术网","neter8.com",]
var a = site.popLast() //"neter8.com"

 7、判断数组

使用==可以检查数组是否相等,使用!=可以判断数据不相等。

var a = ["张三","李四","王五"]
let b = a
let c = ["李四","张三","王五"]
print(a == ["张三","李四","王五"])//true
print(a == b)//true
print(a == c || b == c) //数组内容一样,索引位置不一样返回false
a.removeLast()//删除最后一个
a.append("王五")//重新追加到尾部
print(a == b) //true
a.reserveCapacity(a.count + 10)//分配更多内存空间
print(a == b) //true

数组是有序的这意味着两个内容一样的数组如果顺序不同会被认为不相等的。

8、替换数组

使用replaceSubrange方法可以用指定集合中的元素替换一系列元素。

此方法具有从数组中删除指定范围的元素并在相同位置插入新元素的效果。新元素的数量不需要与被删除的元素数量相匹配。

在此示例中,整数数组中间的三个元素被Repeated实例的五个元素替换。

var nums = [10, 20, 30, 40, 50]
nums.replaceSubrange(1...3, with: repeatElement(1, count: 5))
print(nums) // Prints "[10, 1, 1, 1, 1, 1, 50]"

 三、查找数组

1、contains(Element) -> Bool

返回一个布尔值,该值指示序列是否包含给定元素。说人话即:检查数组里是否包括被检查的内容。

let cast = ["张三", "李四", "王五", "赵六"]
print(cast.contains("张三"))// Prints "true"
print(cast.contains("李翠花"))// Prints "false"

 注:使用contains(element:)的元素是需要遵循Equatable协议。其底层是遍历数组使用“=”对比元素。

2、allSatisfy((Element) -> Bool) -> Bool

返回一个布尔值,该值指示序列的每个元素是否满足给定谓词。判断里的所有元素是否满足条件。

let number = [1, 2, 3, 4, 5, 6]
print(number.allSatisfy{ $0 > 0 })//所有值都大于0,返回true
print(number.allSatisfy{ $0 > 1 })//1不大于1所以返回false

 3、first(where: (Element) -> Bool) -> Element?

返回序列中满足给定谓词的第一个元素。通过传入参数,筛选第一个符合条件的元素(第一次出现)。

var array: [String] = ["张三", "李四", "四李", "李三", "李五"]
let element = array.first(where: { $0.hasPrefix("李") })
print(element!)//李四

 hasPrefix检测字符串是否以指定的“李”前缀开始。

4、last(where: (Element) -> Bool) -> Element?

这个函数与上面的first用法一样,查找最后一个满足条件的元素。

let numbers = [3, 7, 4, -2, 9, -6, 10, 1]
if let lastNegative = numbers.last(where: { $0 < 0 }) {
    print("数组里最一个小于0的值:(lastNegative)。")//-6
}

 5、firstIndex(of: Element) -> Int?

通过元素找索引第一次出现的位置。

var array: [String] = ["张三", "李四", "四李", "李三", "李五"]
if let index = array.firstIndex(of: "四李"){
    print(index)//2
}
//通过Bool条件查找第一个索引
var array: [String] = ["张三", "李一", "李二", "李三", "李四"]
if let i = array.firstIndex(where: { $0.hasPrefix("李") }) {
    print("排名前面的“李”姓为:(array[i]) !")
}

返回的是一个可空的Int,因为可能根据条件找不到元素。

6、lastIndex(of: Element) -> Int?

和上面用法一样,通过元素查的最后一个的索引位置。

var array: [String] = ["张三", "李四", "李四", "李四", "李四"]
if let index = array.lastIndex(of: "李四"){
    print(index)//最后一次出现的索引是4,里面有很多重复的,只找最后出现的。
}
//通过Bool条件查找最好一个索引
var array: [String] = ["张三", "李一", "李二", "李三", "李四"]
if let i = array.lastIndex(where: { $0.hasPrefix("李") }) {
    print("排名最后的“李”姓为:(array[i]) !")
}

 7、min() -> Element?

返回序列中最小的元素,数组元素的类型需要符合Comparable协议。

let doubles = [67.5, 65.7, 64.3, 61.1, 58.5, 60.3, 64.9]
print(doubles.min())//Optional(67.5)
let strings = ["a", "b", "c", "d", "e", "f", "g"]
print(strings.min())//Optional("a")

 找不到满足条件的元素则会返回nill

8、max() -> Element?

返回序列中最大的元素,数组元素的类型需要符合Comparable协议。

let heights = [67.5, 65.7, 64.3, 61.1, 58.5, 60.3, 64.9]
print(heights.max())//Optional(67.5)
//我猜应该是通过ascii码判断大小的
let strings = ["i", "ii", "iii", "iv", "v", "vi", "vii"]
print(strings.max())//Optional("vii")

 找不到满足条件的元素则会返回nill

四、选择元素

1、prefix(Int) -> ArraySlice

返回子序列,最大长度为参数maxLength,其中包含集合的初始元素。 

let numbers = [1, 2, 3, 4, 5]
print(numbers.prefix(0))//maxLength必须大于等于0,返回[]
print(numbers.prefix(2))// Prints "[1, 2]"
print(numbers.prefix(4))// Prints "[1, 2, 3, 4]"
print(numbers.prefix(100))// Prints "[1, 2, 3, 4, 5]"

 当超过索引位置时,则返回到最后一个元素为止。

2、suffix(Int) -> ArraySlice

用法同上,返回的开始位置不同,suffix从索引最后的位置倒数。

let numbers = [1, 2, 3, 4, 5]
print(numbers.suffix(0))//maxLength必须大于等于0,返回[]
print(numbers.suffix(2))// Prints "[4, 5]"
print(numbers.suffix(4))// Prints "[2, 3, 4, 5]"
print(numbers.suffix(100))// Prints "[1, 2, 3, 4, 5]"

五、排除元素

1、dropFirst(Int) -> ArraySlice

返回子序列,包含除给定数目外的所有初始元素。

let numbers = [1, 2, 3, 4, 5]
//dropFirst(k)从集合开始处除除的元素数。K必须大于等于零。
print(numbers.dropFirst(2))// Prints "[3, 4, 5]"
print(numbers.dropLast(4))//dropLast用法和dropFirst一样
print(numbers.dropFirst(100))// Prints "[]"

如果要删除的元素数量超过集合中的元素数量,则结果为空子序列。

2、dropLast(Int) -> ArraySlice

返回子序列,包含除指定数量的元素外的所有元素。

let numbers = [1, 2, 3, 4, 5]
    //dropLast(k)从集合结束位置倒数开始排除的元素数。K必须大于等于零。
print(numbers.dropLast(2))// Prints "[1, 2, 3]"
print(numbers.dropLast(4))//Prints "[1]"
print(numbers.dropLast(100))// Prints "[]"

使用方法同上,顺序与上相反。

3、drop(while: (Element) -> Bool) -> ArraySlice

通过跳过元素返回子序列,而predicate返回true并返回剩下的元素。

let numbers = [3, 7, 4, -2, 9, -6, 10, 1]
print(numbers.drop(while: { $0 > 0 }))//[-2, 9, -6, 10, 1]

 跳过所有大于0的元素。

4、filter((Element) -> Bool) -> [Element]

将满足条件的元素返回给一个新的数组。

var number = [1, 1, 2, 3, 5, 8, 13, 21,34]
let smallNumber = number.filter{ $0 < 8 }//返回小于8的元素[1, 1, 2, 3, 5] 

//下面是一个综合应用,将String转为Int数组的案例
let stringNumber = ["1", "2", "3", "4", "5", "6", "7","张三"]
//[Optional(1), Optional(2), Optional(3), Optional(4), Optional(5), Optional(6), Optional(7), nil]
let conversionInt = stringNumber.map{ Int($0) }//返回Optional类型,使用compactMap会过滤nil

//使用as! 转换数组类型,过滤掉nil值后,将数组强制转换成Int类型数组
let numberAee = conversionInt.filter{ $0 != nil } as! [Int]
print(numberAee) //[1, 2, 3, 4, 5, 6, 7]

 过滤函数在日常开发中经常用到,需要熟悉使用。当接口传入的数据值不正确的时候会崩溃APP,所以我们需要使用过滤处理这些不正确的值 。

六、数组转换

1、map((Element) -> T) -> [T]

map 函数返回一个数组,其中包含在序列元素上映射给定闭包的结果。调用map函数会遍历数组 ,每次取出一个元素保存到闭包的接收参数$0里,然后在闭包代码块里执行我们需要的代码并依次返回给新的接收数据。该接收数组的类型为闭包代码块处理后的返回类型。

var number = [1, 1, 2, 3, 5, 8, 13, 21,34]
//闭包返回的完整写法
let arrSmallNumber:[Bool] = number.map{ num in
   return num <= 8 //[true, true, true, true, true, true, false, false, false]
}

//闭包返回的简写代码
let arrBigNumber:[Bool] = number.map{ $0 > 8 }
//[false, false, false, false, false, false, true, true, true]

在官方的这个例子中,map首先用于将数组中的名称转换为小写字符串,然后计算它们的字符数。

let cast = ["Vivien", "Marlon", "Kim", "Karl"]
let lowercaseNames = cast.map { $0.lowercased() }
//使用lowercased将字符转为了小写:["vivien", "marlon", "kim", "karl"]
let letterCounts = cast.map { $0.count }
//将统计后每个字符长度返回到数组里[6, 6, 3, 4]

 map函数有一个Transform参数接受此序列的一个元素作为其参数关闭映射。,并返回经过转换的相同或不同类型的值。返回包含该序列转换后的元素的数组。

如果需要让数据只返回满足条件的元素,请使用.filter过滤数组。

2、flatMap((Element)

flatMap返回的是一个数组,其中包含使用该序列的每个元素调用给定转换的串联结果。

let stringNumber = [1, 2, 3, 4, 5, 6] 

let stringArr:[String] = stringNumber.flatMap{ num in
    return Array(repeating: String(num), count: num)
}
//["1", "2", "2", "3", "3", "3", "4", "4", "4", "4", "5", "5", "5", "5", "5", "6", "6", "6", "6", "6", "6"]
//简写 let stringArr:[String] = stringNumber.flatMap{ Array(repeating: String($0), count: $0)}
//注意与map函数的区别
let mapped = stringNumber.map { Array(repeating: $0, count: $0) }
// [[1], [2, 2], [3, 3, 3], [4, 4, 4, 4], [5, 5, 5, 5, 5], [6, 6, 6, 6, 6, 6]]

 当转换为每个元素生成序列或集合时,使用此方法接收单层集合。我们可以将flatMap理解为Array(s.map(transform).join())。flatMap是map返回的2维数组的合并后的新数组。

3、compactMap

返回一个数组,其中包含使用该序列的每个元素调用给定转换的非空结果。

let possibleNumbers = ["1", "2", "three", "李四", "王5"]
let mapped: [Int?] = possibleNumbers.map {  Int($0) }
// [Optional(1), Optional(2), nil, nil, nil]
 

//使用compactMap就可过滤掉nil值,也不需要使用as!强制转换数组类型
let compactMapped: [Int] = possibleNumbers.compactMap { Int($0) }// [1, 2]

 知道了与map的区别,就能理解compactMap是怎样使用的。我们可以把compactMap理解为map + filter函数,先对数组类型转换,然后再过滤掉nil值,compactMap常常用于数据的转换与过滤nil值。这是一个非常实用的功能函数。

4、reduce(Result, (Result, Element) -> Result) -> Result

返回使用给定闭包组合序列元素的结果。使用reduce(_:_:)方法从整个序列的元素生成单个值。例如,您可以对一组数字使用此方法来查找它们的和或乘积。 nextPartialResult闭包被依次调用,并将累积值初始化为initialResult和序列中的每个元素。这个例子展示了如何找到一个数字数组的和。

var number = [1, 2, 3, 4, 5, 6, 7,8]
var i=0
let sumNumber = number.reduce(0){ result , num in
    print(i,result) //第一次运行接收初始值0,以后每次都保存上一次返回的结果
    print(i,num) //每次从数组取出遍历的元素
    print("----")
    i += 1
    return result + num//这里改为乘法则永远返回 0 * num = 0
}
//sum = number.reduce(0){ $0+ $1 } //简写
print(sum) // 36

 给reduce传入一个初始值,当闭包第一次循环的时候result即等于这个初始值,num为每次被遍历的元素值,从2次开始result则等于上次闭包返回的结果(return result + num)。

5、lazy: LazySequence

包含与此序列相同元素的序列,但在其上延迟实现某些操作,如映射(map)和筛选(filter)。

var numbers: [Int] = [1, 2, 3, 6, 9]
let modifiedLazyNumbers = numbers.lazy
    .filter { number in
        print("惰性偶数滤波器")
        return number % 2 == 0
    }.map { number -> Int in
        print("加倍数量")
        return number * 2
    }
print(modifiedLazyNumbers)//使用lazy关键字的不会被调用
print(modifiedLazyNumbers.first!)//实例调用才会执行过滤或者映射操作

 七、数据排序

1、sort()

数组排序,将直接修改数组元素的顺序。

var numbers: [Int] = [8, 2, 3, 6, 9]
numbers.sort()//[2, 3, 6, 8, 9]
//字符串排序
var string = ["x","a", "b", "c", "f", "e"]
string.sort()//["a", "b", "c", "e", "f", "x"]

2、sorted() -> [Element]

数组排序,不会直接修改数组元素的顺序,将修改后的结果返回。

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

 3、reverse()

直接反转数组的元素,数组不能使用let定义。

var number = [1, 2, 3, 4, 5, 6]//let定义数组将无法修改
number.reverse()//[6, 5, 4, 3, 2, 1]

var string = ["x","a", "b", "c", "f", "e"]
string.reverse()//["e", "f", "c", "b", "a", "x"]

 4、reversed() -> ReversedCollection

不直接修改数组元素,将修改后的结果返回。所以可以使用let定义。

let number = [1, 2, 3, 4, 5, 6]//可以使用let定义
let reversedNumber:[Int] = number.reversed()//[6, 5, 4, 3, 2, 1]
print(reversedNumber)//[6, 5, 4, 3, 2, 1]
print(String("Hello, world!".reversed()))//!dlrow ,olleH //反转字符串

5、shuffle()

可以打乱数组的索引,让数组随机重新排列数组元素。就是让数组重新洗牌的效果。

var stringNumber = [1, 2, 3, 4, 5, 6]//let定义数组将无法修改
stringNumber.shuffle()//[2, 6, 5, 4, 3, 1]

如果需要定义随机因子,需要使用using参数。names.shuffle(using: myGenerator)

6、shuffled() -> [Element]

shuffle与shuffled的区别在于,shuffled不会直接修改原来的值,而是将打乱后的结果返回。而shuffle则是直接打乱当前数组,所以调用shuffle的数组不能通过let定义。

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

 7、partition(by: (Element) -> Bool) -> Int

对集合中的元素重新排序,以便所有与给定谓词匹配的元素都位于所有不匹配的元素之后。

var string = ["x","a", "b", "c", "f", "e"]
string.partition(by: { $0 == "c" })//["x", "a", "b", "e", "f", "c"]

 
var numbers = [30, 40, 20, 30, 30, 60, 10]
let p = numbers.partition(by: { $0 > 30 })
//[30, 10, 20, 30, 30, 60, 40] 

//数字数组现在被安排在两个分区中。第一个分区,编号[..<p],由不大于30的元素组成。
//第二个分区,数字[p…,由大于30的元素组成。
let first = numbers[..<p] //[30, 10, 20, 30, 30]
let second = numbers[p...] // [60, 40]

 换句话说,把匹配的结果挪到数组的后面。

8、swapAt(Int, Int)

在集合的指定索引处交换值。输入2个元素的索引值,直接修改集合的值,所以不能使用let定义。

var string = ["x","a", "b", "c", "f", "e"]
string.swapAt(string.startIndex, string.endIndex - 1)
print(string)//["e", "a", "b", "c", "f", "x"]

八、操作数组的索引

1、index(after: Int) -> Int

返回一个索引,该索引是到给定索引的指定距离。

//数组的起始索引中获取前4个位置的索引,然后打印该位置的元素。
let numbers = [10, 20, 30, 40, 50]
let i = numbers.index(numbers.startIndex, offsetBy: 4)
print(numbers[i])// Prints "50"
//限制结束位置
let j = numbers.index(numbers.startIndex,
                      offsetBy: 10,
                      limitedBy: numbers.endIndex)
print(j) // Prints "nil"
//Limit 要用作限制的集合的有效索引。如果距离> 0,如果距离小于i, limit没有影响。
//同样,如果距离< 0,如果距离大于i, limit没有影响。

//返回紧跟着的索引。
let i = numbers.index(after: 1)//必须小于endIndex。

//返回前面的索引。
let i = numbers.index(before: 1)//必须大于startIndex。

 2、formIndex(after: inout Int)

传入一个索引值Int的指针,这个值不能大于startIndex,然后将指针指向下一个索引位置。因为要修改指针指向,所以 i 必须使用var定义。 

let numbers = ["张三", "李四","王五","赵六","刘七"]

var i = numbers.startIndex //定义i的初始索引为0
numbers.formIndex(after: &i)//传入i的指针,并修改指针指向下一个索引“1”
print(numbers[i],i) //李四 1
print(numbers[i-1],i-1) //张三 0

i = numbers.endIndex - 1 //定义i的索引为 4 - 1 = 3
numbers.formIndex(before: &i)//传入i的指针,并修改指针指向下一个索引“4”
print(numbers[i],i) //赵六 3
print(numbers[i-1],i-1) //王五 2

i = numbers.endIndex //定义i的索引为4
numbers.formIndex(&i, offsetBy: -1)//偏移-1
print(numbers[i],i) //刘七 4

i = numbers.startIndex //定义i的初始索引为0
//当偏移位置超过了limitedBy限制则处理失败。
if numbers.formIndex(&i, offsetBy: 1,limitedBy: numbers.endIndex){
    print(numbers[i],i)//王五 2
}else{
    print("offsetBy超过索引范围/超过了limitedBy限制,偏移失败")
}

3、distance(from: Int, to: Int) -> Int

返回从from开始到to的距离值。

let numbers = Array(0...100)//定义101个集合
var i = numbers.distance(from: numbers.startIndex, to: numbers.endIndex)//返回两个索引之间的距离。
print("从startIndex到endIndex的距离为:",i)//从startIndex到endIndex的距离为: 5

 九、数组比较

1、elementsEqual(OtherSequence) -> Bool

如果此序列和其他序列包含相同顺序的相同元素,则为True。

//返回一个布尔值,该值指示该序列和另一个序列是否以相同顺序包含相同的元素。
let string = ["张三", "李四","王五"]
print(string.elementsEqual(["张三","王五", "李四"])) // Prints "false"

let a = 1...3
let b = 1...10
print(a.elementsEqual(b)) // Prints "false"
print(a.elementsEqual([1, 2, 3])) // Prints "true"

 2、starts(with: PossiblePrefix) -> Bool

返回一个布尔值,指示序列的初始元素是否与另一个序列中的元素相同。

let a = 1...3
let b = 1...10
print(b.starts(with: a))// Prints "true"
print(b.starts(with: []))// Prints "true"

let string = ["张三", "李四","王五"]
print(string.starts(with: ["张三","李四"])) // Prints "true"

 十、数组高级用法

每个数组都保留特定数量的内存来保存其内容。当您向数组添加元素并且该数组开始超出其保留容量时,该数组会分配更大的内存区域并将其元素复制到新存储中(创建新的数组内存空间,指针从旧的内存空间指向新创建的并回收旧空间)。新存储是旧存储大小的倍数。这种指数增长策略意味着附加元素在恒定时间内发生,从而平均许多附加操作的性能。触发重新分配的追加操作具有性能成本,但随着数组变大,它们发生的频率越来越低。

1、分配数组内存空间

如果要向数组中添加元素,并且count值超出其内存容量(capacity值),则该数组必须增加其容量。由于Swift数组将其元素连续存储在内存中(内存地址连续寻址速度更快),因此它必须重新分配其内部存储,并且(通常)将其所有元素从旧存储复制到新存储。

如果您知道大约需要存储多少个元素(事先知道要向阵列中添加多少个元素),请在追加到数组之前使用reserveCapacity方法预设阵列的容量以避免中间重新分配。使用和属性确定数组可以存储多少个元素而无需分配更大的存储空间,这样它就不需要执行任何重新分配(和关联的复制)。

var Numbers: [Int] = []
print(Numbers.capacity)//系统自动分配的内存空间(空数组为0)
Numbers.reserveCapacity(Numbers.count + 10)//在原来的础上增加1个内存空间
print(Numbers.capacity)//手动增加后的内存空间(打印10)

这里我们要了解count 与 capacity的区别,count就是数组现有的元素数量,capacity是容量(内存分配),表示在现在的结构中能存放的元素数。但是swift的array是动态的,所以当capacity满了以后会自动扩充,这一动作对用户是隐藏的。在一般情况下,我们只有在优化使用内存时才会访问capacity的值。

var array = [1, 2, 3, 4, 5,6,7,8,9]
array.count   //9
array.capacity //9
array.removeLast()//删除后capacity不会改变
array.count   //8
array.capacity //9
//stride 快速生成从一个起始值from到一个结束值through,按指定的数量步进by的序列。
array.append(contentsOf: stride(from: 10, through: 20, by: 2))//capacity在这里开始增加的
array.count //13
array.capacity //18

 对于大多数Element类型的数组,此存储是一个连续的内存块。对于Element类型为类或@objc协议类型的数组,此存储可以是连续的内存块或 的实例NSArray。因为 的任意子类都NSArray可以成为 an Array,所以在这种情况下不能保证表示或效率。

2、同步修改数组的副本值

我们知道Array是使用struct定义的一个值类型,所以当我们使用"="赋值的时候,基本是值的拷贝:

var a = [1, 2, 3, 4, 5]
var b = a //b将a的数据复制了一份到自己的空间
a[0] = 100//修改a[0]不是影响b[0]的值
print(a)//[100, 2, 3, 4, 5]
print(b)//[1, 2, 3, 4, 5]

每个数组都有一个独立的值,其中包括其所有元素的值。对于诸如整数和其他结构之类的简单类型,这意味着当您更改一个数组中的值时,该元素的值在该数组的任何副本中都不会更改。

如果数组中的元素是类的实例(引用类型),则语义是相同的,尽管它们一开始可能会有所不同。在这种情况下,数组中存储的值是对数组外部对象的引用。如果更改对一个数组中某个对象的引用,则只有该数组具有对新对象的引用。但是,如果两个数组包含对同一个对象的引用,您可以从两个数组中观察到该对象属性的变化。

如果在开发需求中我们希望当修改a[0]的时候,b[0]的值也要发生改变的话,我们就需要将数据封闭到一个引用类型中,使用内存地址指向去修改实际面目存储的值:

//定义一个引用类型
class IntegerReference {
    var value = 10
}
//将引用类型放入到数组里
var a = [IntegerReference(), IntegerReference()]

//b复制的是a保存的一个内存地址,他们指向了同一个存储位置
var b = a //虽然是值拷贝,但复制的是内存地址

a[0].value = 100 //这里修改a[0]的内存地址指向的值为100
print(b[0].value)//打印发现b[0]被修改成了100

//重新让a[0]指向新的内存地址
a[0] = IntegerReference()
print(a[0].value) //"10"

//b[0]依然指向老地址,所以与a[0]的位置不再一样
print(b[0].value) //"100"

 3、写时复制

数组与标准库中的所有可变大小集合一样,使用写时复制优化。阵列的多个副本共享相同的存储,直到您修改其中一个副本。发生这种情况时,被修改的阵列会用自己唯一拥有的副本替换其存储,然后就地修改该副本。有时会应用优化以减少复制量。

这意味着如果一个数组与其他副本共享存储,则对该数组的第一个变异操作会产生复制该数组的成本。作为其存储唯一所有者的阵列可以就地执行变异操作。

在下面的示例中,a创建了一个阵列以及共享相同存储的两个副本。当原始a数组被修改时,它会在进行修改之前制作其存储的唯一副​​本。进一步修改a到位,同时两个副本继续共享原始存储。 

var a = [1, 2, 3, 4, 5]
var b = a //这里不会复制a的值到b
var c = a //这里不会复制a的值到c

//当修改a的时候,将把a复制给b和ca
a[0] = 100
a[1] = 200
a[2] = 300
// 'a' 的值 [100, 200, 300, 4, 5]
// 'b' 和 'c' 的值 [1, 2, 3, 4, 5]

4、数组返回随机元素

使用randomElement()方法返回集合中的一个随机元素。如果集合为空,则该方法返回nil。

let names = ["张三", "李四", "王五", "赵六"]
let randomName = names.randomElement()//Optional("赵六")
if let randomName = names.randomElement(){
    print(randomName) //赵六
}

 使用自己的随机数发生器: 

let names = ["张三", "李四", "王五", "赵六"]
var myGenerator = SystemRandomNumberGenerator()//系统随机数发生器
if let randomName = names.randomElement(using: &myGenerator){
    print(randomName) //赵六
}

 5、ArraySlice 数组切片

数组的切片可以快速有效地对较大数组的部分执行操作。一个实例不是将切片的元素复制到新的存储中,而是提供了一个更大阵列存储的视图。并且因为呈现与 相同的接口,您通常可以在切片上执行与原始数组相同的操作。

数组起始索引并不总是[0]。切片为相同的元素维护较大数组的相同索引,因此切片的起始索引取决于它的创建方式,让您可以对完整数组或切片执行基于索引的操作。安全访问数组切片请使用endIndex与startIndex;

var absences = [1, 1, 2, 3, 5, 8, 13, 21,34]

var midpoint = absences.count / 2 //中间位置
var firstHalf = absences[..<midpoint] //前半部分切片
var secondHalf = absences[midpoint...]//后半部分切片

let firstHalfSum = firstHalf.reduce(0, +)
let secondHalfSum = secondHalf.reduce(0, +)
if firstHalfSum > secondHalfSum {
    print("前半部分“和”更大")
} else {
    print("后半部分的“和”更大")
}

firstHalf[firstHalf.startIndex] = 100 //修改数组切片的值不会影响源数组 print(absences[0])

if let i = absences.firstIndex(where: { $0 > 1 }) {//查找一个元素值大于1的索引
    let absencesAfterFirst = absences[(i + 1)...]                   // 从3开始返回数组切片
    if let j = absencesAfterFirst.firstIndex(where: { $0 > 2 }) {   // 3
        print("absences数组里第(i)个索引的元素值大于1:(absences[i])")    // 4
        print("absences数组里第(j)个索引的元素值大于2: (absences[j])")
    }
}

 数组切片的设计主要为了共享索引,共享索引是 Swift 集合算法设计的一个重要部分。

absences[firstHalf.endIndex] = 999 //数组切片设计的最终目的是共享索引
print("firstHalf.endIndex在absences的索引值:",absences.firstIndex(of: absences[firstHalf.endIndex])!)
    //打印:firstHalf.endIndex在absences的索引值: 4

数组切片的其它定义和应用方式: 

let array = [5, 2, 10, 1, 0, 100, 46, 99]
array[...2] // ArraySlice [5, 2, 10] 从0到2
array[..<2] // ArraySlice [5, 2] 从0到1(小于2)
array[6...] // ArraySlice [46, 99] 从6从最后(7)
array[...] // ArraySlice [5, 2, 10, 1, 0, 100, 46, 99] 全部

array.prefix(4) // ArraySlice[5、2、10、1] 前4个元素,不包括索引“4”中的元素
array.prefix(upTo: 4) // ArraySlice[5, 2, 10, 1]// 索引“4”之前的第一个元素,包括索引“4”中的元素
array.prefix(through: 4) // ArraySlice[5, 2, 10, 1, 0]// 在条件失败之前的第一个元素(在这种情况下,元素必须小于10)
array.prefix { $0 < 10 } // ArraySlice[5, 2]


array.suffix(3) // ArraySlice[100、46、99] 最后3个元素
array.suffix(来自:5)// ArraySlice[100,46,99] 索引5中的最后元素,包括索引5中的元素

将ArraySlice转换为Array:

var array = [5、2、10、1、0、100、46、99]
let slice = array.dropLast(5)
array = slice // 无法将类型“ArraySlice”的值分配给类型“[Int]”
array = Array(slice) // [5, 2, 10] 需要使用array转换一下即可
除非注明,网络人的文章均为原创,转载请以链接形式标明本文地址:http://www.neter8.com/ios/163.html
标签:数组arrayKwok最后编辑于:2021-10-11 10:11:54
0
感谢打赏!

《深入学习swift数组(Array)看这一篇就够了》的网友评论(0)

本站推荐阅读

热门点击文章