Array的startIndex的崩溃分析

430 阅读2分钟

如果你也有这样的疑问:为啥对一个ArraySlice再次切片时,和我想要的结果不一样呢?请看下文。 下文是记录一次开发中遇到的问题,代码是实现归并排序算法。测试用例是[8 , 4 , 7, 3],myArraySlice的数据类型是ArraySlice。

崩溃1和分析

//代码
    private func mergeSortSlice(_ myArraySlice: ArraySlice<Element>) -> ArraySlice<Element> {
        if myArraySlice.count < 2 {
            return myArraySlice
        }else {
            let midIndex = myArraySlice.count / 2
            print("\(midIndex) -- \(myArraySlice.startIndex) -- \(myArraySlice.endIndex)")
            let leftSlice = myArraySlice[myArraySlice.startIndex..<midIndex]
            let rightSlice = myArraySlice[midIndex..<myArraySlice.endIndex]
            
            print(leftSlice)
            print(rightSlice)
            let slice1 = mergeSortSlice(leftSlice)
            print(slice1)
            let slice2 = mergeSortSlice(rightSlice)
            print(slice2)
            return merge(slice1, slice2)
        }
    }

//打印
2 -- 0 -- 4
[8, 4]
[7, 3]
1 -- 0 -- 2
[8]
[4]
[8]
[4]
[4, 8]
1 -- 2 -- 4
Fatal error: Can't form Range with upperBound < lowerBound

//分析
myArraySlice是方法传进来的实参,它的小标不是从0开始的,所以myArraySlice.startIndex..<midIndex,出现了左边的值比右边的值还大的情况。

崩溃2和分析

//代码
            let midIndex = myArraySlice.count / 2
            print("\(midIndex) -- \(myArraySlice.startIndex) -- \(myArraySlice.endIndex)")
            let leftSlice = myArraySlice[0..<midIndex]
            let rightSlice = myArraySlice[midIndex..<myArraySlice.count]
            print(leftSlice)
            print(rightSlice)
            

//打印
2 -- 0 -- 4
[8, 4]
[7, 3]
1 -- 0 -- 2
[8]
[4]
[8]
[4]
[4, 8]
1 -- 2 -- 4
Fatal error: ArraySlice index is out of range (before startIndex)

//分析
同上,myArraySlice是方法传进来的实参,它的小标不是从0开始的,所以0..<midIndex,比startIndex还小。

解决的code

//代码
            let midIndex = myArraySlice.count / 2
            print("\(midIndex) -- \(myArraySlice.startIndex) -- \(myArraySlice.endIndex)")
            let newSlice = ArraySlice.init(myArraySlice)
            print("\(midIndex) -- \(newSlice.startIndex) -- \(newSlice.endIndex)")
            let leftSlice = newSlice[0..<midIndex]
            let rightSlice = newSlice[midIndex..<newSlice.count]
            print(leftSlice)
            print(rightSlice)

//打印
2 -- 0 -- 4
2 -- 0 -- 4
[8, 4]
[7, 3]
1 -- 0 -- 2
1 -- 0 -- 2
[8]
[4]
[8]
[4]
[4, 8]
1 -- 2 -- 4
1 -- 0 -- 2
[7]
[3]
[7]
[3]
[3, 7]
[3, 4, 7, 8]

//分析
新建一个当前切片的副本,那么这个副本的下标就从0开始了。

更好解决的code

上面虽然解决了,用0和count来平分ArraySlice,但是新建了一个ArraySlice,改变了下标的范围,占用内存。下面不改变原ArraySlice的下标,直接startIndex和endIndex计算中间的下标,最后平分ArraySlice。推荐使用这种方式。

//代码
            let midIndex = (myArraySlice.startIndex + myArraySlice.endIndex) / 2
            print("\(midIndex) -- \(myArraySlice.startIndex) -- \(myArraySlice.endIndex)")
            let leftSlice = myArraySlice[myArraySlice.startIndex..<midIndex]
            let rightSlice = myArraySlice[midIndex..<myArraySlice.endIndex]

总结

  • 对数组做切片后,局部切片的下标值不变,虽然长度变小了,但是不能从下标0取值或者在进行切片。如果要用下标0..<(count/2)和(count/2)..<count来平分当前切片,需要新建一个当前切片的副本,那么这个副本的下标就从0开始了。

  • 如果用startIndex和endIndex从数组取值,这2个值还是在原数组中的下标,所以计算当前切片长度中间的下标值的时候,要用这2个下标的和除以2。对当前切片在进行更小的平分切片时,可以用startIndex、中间下标和endIndex来切分。

Demo

实现归并排序算法demo