Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Modifying Arrays in Swift, (Inserting and Appending via Subscript Syntax)

Tags:

arrays

ios

swift

So I've been doing some experiments with Swift's array. And discovered some few things with the following codes below:

var shoppingList = ["Chocolate", "Bread", "Cheese", "Apple"]
println(shoppingList)
shoppingList[2..<2] = ["Butter", "Milk"] //Question #1 (Insert after 2nd element)
println(shoppingList)
shoppingList[0..<0] = ["Coffee"]
println(shoppingList)
shoppingList[7..<7] = ["Berry", "Pie"] //Question #2 (Append at the end of array)
println(shoppingList)

Gives the following output:

[Chocolate, Bread, Cheese, Apple]
[Chocolate, Bread, Butter, Milk, Cheese, Apple]
[Coffee, Chocolate, Bread, Butter, Milk, Cheese, Apple]
[Coffee, Chocolate, Bread, Butter, Milk, Cheese, Apple, Berry, Pie]

Question #1: Experimenting with the subscript syntax, it seems that using ..< will Insert an item to the array to the given position (which in my sample is after 2nd element via [2..<2]. As opposed to call array's insert(_:atIndex:) method, does this have any differences on performance? Or depending on the scenario, readability will just be the benefit of using insert()?

Question #2: According to Swift's documentation here: Collection Type - See Modifying/Accessing Arrays

NOTE

You can’t use subscript syntax to append a new item to the end of an array.

Then my question is why was I able to append 2 elements, "Berry" and "Pie" to the end of array by using [7..<7]?

I am using Swift 1.2 at the time of this writing.

like image 696
Joel Legaspi Enriquez Avatar asked Oct 20 '25 04:10

Joel Legaspi Enriquez


1 Answers

This looks like a great question, so I've had a play around.

Looking at the example of subscripting with 7..<7, the half-open range operator ..< is creating a Range struct that looks like this:

let range = 7..<7
print(range.startIndex)    // 7
print(range.endIndex)      // 7

Which is perhaps obvious. But what impact does a range with an end index beyond the array bounds have on the array subscript? The subscript function declaration looks like this:

subscript (subRange: Range<Int>) -> ArraySlice<Element>

And if we were to call

let shoppingList = ["Coffee", "Chocolate", "Bread", "Butter", "Milk", "Cheese", "Apple"]
let item = shoppingList[6..<7]
// item: ArraySlice<Int> = "Apple"

We get back a slice of "Apple", which you would expect - we've asked for a slice of shoppingList containing elements at indexes 6 or higher and indexes lower than 7, so we get back a slice with a single item at index 6.

Also note this behaviour:

let item = shoppingList[6...6]
print(range.startIndex)    // 6
print(range.endIndex)      // 7

The endIndex is actually 7, but looking at the subscript above you wouldn't expect any problems, and you wouldn't get any. One way to look at this is to say that the endIndex of the sub range is the index of the first array element not to be included in the array slice. We can assume that the subscript implementation bounds-checks with this in mind.


So what might an implementation of this subscript's getter look like? Let's try it:

extension Array {
    func myRangeSubscript(subRange: Range<Int>) -> ArraySlice<Element> {
        if count < subRange.endIndex { fatalError("Out of bounds!") } 

        var slice = ArraySlice<Element>()
        for i in subRange {
            slice.append(self[i])
        }
        return slice
    }
}

When passing in 7..<7, since no integer exists for x where7 >= x < 7, the for loop isn't entered once. That clears up why shoppingList[7..<7] works, despite appearing at first glance to be indexing out of bounds.

It's only a small step further to consider what the subscript assignment might look like:

subscript(subRange: Range<Int>) -> ArraySlice<Element> {
    get {
        //...
    }
    set(newElements) {
        // newElements: Array<Element> = []
        self.removeRange(subRange)
        var elementIndex = 0
        for i in subRange {
            self.insert(newElements[elementIndex++], atIndex: i)
        }
    }
}


Perhaps the reason you can apparently "append" to the end of the array despite the documentation saying otherwise, is that semantically you're not actually appending - you're replacing a range of elements in the array with a new array of elements. It just so happens that with the n..<n range, you're replacing an empty array of elements with your new array. Technically, perhaps, not an append/insert, but a rather quirky replace! So while the docs might be technically correct, in practice they just don't consider this edge case.



Briefly, regarding the question of performance, I cannot say with certainty how the subscript is actually implemented. But it seems obvious that it would take at least as long to perform an insert using this range subscript as it would to use insert(_:atIndex:) or the single index subscript (which very likely calls through to insert(_:atIndex:) anyway), since there could be some overhead in preparing for an ArraySlice to be created, or for a call to removeRange(_:).

For this reason, and the fact that it is less readable and not semantically reflecting what you're trying to do, I would avoid using this range subscript to insert array items. Just stick with insert(_:atIndex:) or the corresponding subscript - that's what they're there for!

like image 101
Stuart Avatar answered Oct 21 '25 19:10

Stuart



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!