Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make a safe Array subset in Swift?

Tags:

arrays

ios

swift

I'm trying to make a safe way of splitting an array.

I know you can take a subset of an array doing something like this:

let arr = [1,2,3,4,5]
print(arr[0..<3])

It will print [1,2,3]

If you try the following on that same arr

print(arr[3..<9])

The program will crash

I want to make an array extension that will instead of crashing, yield as many of the elements as it can so it would print [4,5]

subscript(safe range: Range) -> Element? {

}
like image 639
shakked Avatar asked Mar 27 '16 15:03

shakked


People also ask

What is ArraySlice in Swift?

Overview. The ArraySlice type makes it fast and efficient for you to perform operations on sections of a larger array. Instead of copying over the elements of a slice to new storage, an ArraySlice instance presents a view onto the storage of a larger array.

Can we convert array to set in Swift?

You don't have to reduce an array to get it into a set; just create the set with an array: let objectSet = Set(objects.

Which one is faster array or set in Swift?

Array is faster than set in terms of initialization. Set is slower than an array in terms of initialization because it uses a hash process. The array allows to store duplicate elements in it. Set doesn't allow to store duplicate elements in it.


4 Answers

You could do the following:

extension Array {
    subscript(safe range: Range<Index>) -> ArraySlice<Element>? {
        if range.endIndex > endIndex {
            if range.startIndex >= endIndex {return nil}
            else {return self[range.startIndex..<endIndex]}
        }
        else {
            return self[range]
        }
    }
}

let a = [1,2,3]
a[safe: 1...3] // [2,3]

Edit: given the comment that the start index might not be the beginning of the array, I've amended so that the returned slice will always begin at startIndex even if the endIndex goes beyond bounds of the Array (unless start index is after endIndex of Array in which case nil is returned).

like image 80
sketchyTech Avatar answered Sep 22 '22 12:09

sketchyTech


EDIT: Updated to a more straight forward version.

Just made for the practice. Same safe naming as the others used for clarity; note that it does not return nil but an empty array for out of bounds indexing, that avoids null checks in the consuming code for many cases.

extension Array {
    subscript(safe range: Range<Index>) -> ArraySlice<Element> {
        return self[min(range.startIndex, self.endIndex)..<min(range.endIndex, self.endIndex)]
    }
}

let a = [1,2,3]
a[safe: 1..<17] // [2,3]
a[safe: 4..<17] // []
a[safe: 1..<2]  // [2]

...or an alternate - more straight forward - version;

like image 26
Joachim Isaksson Avatar answered Sep 18 '22 12:09

Joachim Isaksson


You can use the

extension RandomAccessIndexType {
    @warn_unused_result
    public func advancedBy(n: Self.Distance, limit: Self) -> Self
}

method to limit the given range to the valid range of the given array:

extension Array {

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

        let from = startIndex.advancedBy(subRange.startIndex, limit: endIndex)
        let to = startIndex.advancedBy(subRange.endIndex, limit: endIndex)
        return self[from ..< to]
    }
}


let arr = [1,2,3,4,5]
print(arr[safe: 3..<10]) // [4, 5]
print(arr[safe: 9..<10]) // []

Update for Swift 3: Indexing collections has changed a lot. Now you can use the index(...) methods defined in the BidirectionalIndexable protocol:

extension Array {

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

        let from = index(startIndex, offsetBy: subRange.lowerBound, limitedBy: endIndex) ?? endIndex
        let to = index(startIndex, offsetBy:  subRange.upperBound, limitedBy: endIndex) ?? endIndex
        return self[from ..< to]
    }
}
like image 34
Martin R Avatar answered Sep 20 '22 12:09

Martin R


You should prefer an empty collection over an optional collection. This works well with swift 4.

extension Array {
    /*
     Safe array access via range, accounting for array bounds
     examples:
     [1, 2, 3][0...6] -> []
     [1, 2, 3][0...1] -> [1, 2]
     */
   subscript(safe range: Range<Index>) -> [Element] {
        guard
            range.startIndex >= self.startIndex,
            range.endIndex <= self.endIndex
            else {
                return []
        }

        return Array(self[range])
    }
}
like image 41
thexande Avatar answered Sep 22 '22 12:09

thexande