Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 352
Hristo Avatar asked Sep 09 '15 21:09

Hristo


Video Answer


3 Answers

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.

like image 194
oisdk Avatar answered Oct 16 '22 07:10

oisdk


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)]
    }
}
like image 34
joel.d Avatar answered Oct 16 '22 07:10

joel.d


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"
like image 1
Andy Jazz Avatar answered Oct 16 '22 07:10

Andy Jazz