Hristo
Hristo

Reputation: 6540

Swift – Negative indexing in arrays

Is it possible to override Swift's default array subscripting so that it handles negative indices like Python?

For example, a[-1] should return the last element of a, and a[-2] the element before it.

This should be possible to achieve by extending the Array type, alas, the code below won't work since it would loop infinitely:

extension Array {
   subscript (index:Int) -> [Element] {
        return (index < 0) ? self[self.count+index] : self[index]
   }
}

How bad would the idea of overriding something that fundamental be?

Upvotes: 9

Views: 6708

Answers (3)

Andy Jazz
Andy Jazz

Reputation: 58493

Swift 5.7

@Hristo, Not only can we use a negative indexing in Swift subscript's functionality but also implement error handling to control whether you're "out of range" or not. So, use the following code for that.

let array: [Int] = [199, 288, 377, 455, 533, 622, 711]

enum SubscriptError: Error {
    case greaterThanZero
    case lessThanLastIndex
}

extension Collection {

    public subscript(negative i: Int) -> () throws -> Element {
        
        let backward = i - 1
        
        if i > 0 {
            return { throw SubscriptError.greaterThanZero }
        }
        if i < -1 * ((endIndex as! Int) - 1) {
            print(endIndex)
            return { throw SubscriptError.lessThanLastIndex }
        }           
        return { self[index(endIndex, offsetBy: backward)] }
    }
}

do {
    try array[negative: -6]()                      // 199
} catch {
   print("It's \(error)")
}

The results are as follows:

let array: [Int] = [199, 288, 377, 455, 533, 622, 711]

try array[negative: 2]()       //  "It's greaterThanZero"
try array[negative: 1]()       //  "It's greaterThanZero"
try array[negative: 0]()       //  711
try array[negative: -1]()      //  622
try array[negative: -2]()      //  533
try array[negative: -3]()      //  455
try array[negative: -4]()      //  377
try array[negative: -5]()      //  288
try array[negative: -6]()      //  199
try array[negative: -7]()      //  "It's lessThanLastIndex"
try array[negative: -8]()      //  "It's lessThanLastIndex"

Upvotes: 2

joel.d
joel.d

Reputation: 1631

Swift 4 version:

extension Collection where Index: Comparable {
    subscript(back i: Int) -> Iterator.Element {
        let backBy = i + 1
        return self[self.index(self.endIndex, offsetBy: -backBy)]
    }
}

Upvotes: 7

oisdk
oisdk

Reputation: 10091

Actually, there's a relatively good-practice, Swifty way to do this: labelled arguments.

extension CollectionType where Index : BidirectionalIndexType {
  subscript(back i: Index.Distance) -> Generator.Element {
    return self[endIndex.advancedBy(-i)]
  }
}

let ar = [1, 2, 3, 4]
ar[back: 1] // 4
ar[back: 2] // 3

You can obviously change the semantics pretty easily. This implementation, for instance, requires the index be larger than 0. Changing it so 0 returns the last element is as simple as: self[endIndex.predecessor().advancedBy(-i)], or, if you want to assume the index is negative going in: self[endIndex.advancedBy(-i)].

The advantage of the labelled argument is that it's clear, and no-one would use it by accident.

Upvotes: 18

Related Questions