Swift: Replace a range of values in an array (and do other groovy stuff too) the easy way (Xcode 7.2, Swift 2.1)


An introduction to the easy way

We have a couple of options when it comes to replacing a range of values within an array, and one might call them the hard way and the easy way. Starting with the "hard way", there is a method called replaceRange:with:
var array = [1,2,3,4,5,6,7,8,9,10]
array.replaceRange(Range(start: array.startIndex.advancedBy(1),end: array.startIndex.advancedBy(2)), with: [11,11]) // [1, 11, 11, 3, 4, 5, 6, 7, 8, 9, 10]
Using this method we must supply a Range to begin with and then a collection of the type we are working with to the with: argument. It needn't be as convoluted as the first example, however, because we can do the following:
var array = [1,2,3,4,5,6,7,8,9,10]
array.replaceRange(Range(1...2), with: [11,11]) // [1, 11, 11, 3, 4, 5, 6, 7, 8, 9, 10]
and we can go even further in our code reduction:
var array = [1,2,3,4,5,6,7,8,9,10]
array.replaceRange(1...2, with: [11,11]) // [1, 11, 11, 3, 4, 5, 6, 7, 8, 9, 10]
But the ultimate simplification is to use subscripting ("the easy way"):
var array = [1,2,3,4,5,6,7,8,9,10]
array[1...2] = [11,11] // [1, 11, 11, 3, 4, 5, 6, 7, 8, 9, 10]
A few questions follow.

What happens if the length of the range we supply is larger than the number of values we pass?

The whole range is replaced by the values and the total length of the array shrinks. So if you have a range that is two values in length and you supply one value, the first value in the range is replaced and the second is removed. Here's an example:
var array = [1,2,3,4,5,6,7,8,9,10]
array[1...3] = [11] // [1, 11, 5, 6, 7, 8, 9, 10]

What happens if the length of the range we supply is shorter than the number of values we pass?

If the range is shorter than the number of values then the array grows, because the additional values are inserted in the same way as they would be if you were to use insert:atIndex: or insertContentsOf:at:.
var array = [1,2,3,4,5,6,7,8,9,10]
array[1...3] = [11,11,11,11,11] // [1, 11, 11, 11, 11, 11, 5, 6, 7, 8, 9, 10]

Does this mean there is a way to use the "easy way" as an insert method?

Yes, you can utilise the half-open range operator (..<):
var array = [1,2,3,4,5,6,7,8,9,10]
array[1..<1] = [11] // [1, 11, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Is there a way to use the "easy way" as a delete method as well then?

Yes:
var array = [1,2,3,4,5,6,7,8,9,10]
array[1...1] = [] // [1, 3, 4, 5, 6, 7, 8, 9, 10]
array[3...6] = [] // [1, 3, 4, 9, 10]

What happens if the range we wish to replace goes beyond the length of the array?

Crash! Even if the range starts within the boundaries of the array:
var array = [1,2,3,4,5,6,7,8,9,10]
array[9...10] = [11,11] // Crash

So how do we append using the "easy way"?

You can use subscripting to append a value in the following way:
var array = [1,2,3,4,5,6,7,8,9,10]
array[10..<10] = [11] // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
And if you wish to append a collection of elements, no need to use appendContentsOf: simply write
var array = [1,2,3,4,5,6,7,8,9,10]
array[10..<10] = [11,12,13] // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
What we are doing here is taking the endIndex of the array to make our half-open array, so really it would be clearer to write:
array[array.endIndex..<array.endIndex] = [11,12,13] // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]

Conclusion

While replacing a range is less verbose using subscripting, it could well be argued that removeRange: and appendContentsOf: provide a better statement of intent than the examples above. Especially when we need only write for example:
array.removeRange(1...3)
and
array.appendContentsOf([11,12,13])
In the final case, in particular, when we consider the array.endIndex...<array.endIndex code provided above, subscripting is both more verbose and less clear. And although we can improve things a little using an extension
extension Array {
    var appendPosition: Range<Int> {
     return self.endIndex..<self.endIndex
    }
}
array[array.appendPosition] = [11,12,13] // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
it looks like we are finding excuses not to use the append method available to us. But both options exist, subscripting and instance methods, and in some situations the flexibility of subscripting will determine its use, where we do not discriminate for example between appending, inserting, deleting and so on, and in other situations descriptiveness will rule.


Endorse on Coderwall

Comments

  1. The second example of "easy way" delete method. It should be :

    array[3...6] = [] // [1, 3, 8, 9, 10]

    ReplyDelete

Post a Comment