如何在Swift中创建一个安全的数组子集?

时间:2016-03-27 15:21:08

标签: ios arrays swift

我正在尝试分割数组的安全方法。

我知道你可以把一个数组的子集做成这样的事情:

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

它会打印[1,2,3]

如果您在同一arr

上尝试以下操作
print(arr[3..<9])

程序将崩溃

我想制作一个不会崩溃的数组扩展,尽可能多地生成元素,以便打印 [4,5]

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

}

6 个答案:

答案 0 :(得分:5)

您可以执行以下操作:

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]

编辑:鉴于起始索引可能不是数组的开头的注释,我进行了修改,以便返回的切片始终从startIndex开始,即使endIndex超出了数组的边界(除非开始索引在数组的endIndex之后,在这种情况下返回nil)。

答案 1 :(得分:3)

您可以使用

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

将给定范围限制为给定数组的有效范围的方法:

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]) // []

Swift 3更新:索引集合发生了很大变化。 现在,您可以使用中定义的index(...)方法 BidirectionalIndexable协议:

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]
    }
}

答案 2 :(得分:3)

编辑:更新为更直接的版本。

刚为练习做好准备。与其他用于清晰度的safe命名相同;请注意,它不会返回nil,而是返回一个用于越界索引的空数组,这可以避免在许多情况下对使用代码进行空检查。

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]

...或替代 - 更直接 - 版本;

答案 3 :(得分:0)

来自Array&#39; startIndex的文档:

  

始终为零,这是非空时第一个元素的索引。

因此可以假设

startIndex是第一个元素的索引(如果有的话)。

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

        return Array(self[range])
    }
}

[1, 2, 3, 4, 5][safe: 1..<4] // [2, 3, 4]
[1, 2, 3, 4, 5][safe: 1...7] // nil

答案 4 :(得分:0)

我们可以如下概括马丁对CollectionType s的精细解决方案。此外,我认为我对take(_: Range<Index>)这样的签名更为舒服,因为take(_: Int)传统上同样宽容超出范围语义(与集合&#39; subscript方法相反):

public extension CollectionType where Index.Distance : protocol<IntegerLiteralConvertible, Comparable> {

    public func take(range: Range<Index>) -> SubSequence {

        let dFrom = startIndex.distanceTo(range.startIndex)
        let dTo = startIndex.distanceTo(range.endIndex)

        let from = dFrom <= 0 ? startIndex : startIndex.advancedBy(dFrom, limit: endIndex)
        let to = dTo <= 0 ? startIndex : startIndex.advancedBy(dTo, limit: endIndex)

        return self[from..<to]
    }
}

public extension CollectionType {
    public var array: [Generator.Element] { return Array(self) }
}

let collection = 1...5

collection.take(9...10).array           // []
collection.take(1...3).array            // [2, 3, 4]
collection.take((-3)...10).array        // [1, 2, 3, 4, 5]
collection.take((-5)...(-2)).array      // []

仅需要约束Index.Distance : protocol<IntegerLiteralConvertible, Comparable>才能优雅地处理小于startIndex的索引范围......

请注意,将此应用于String.characters需要:

let s = "random string"
s.characters.take(s.startIndex.advancedBy(3)...s.startIndex.advancedBy(5)).array // ["d", "o", "m"]

不太好。但是,除了上面更通用的代码之外,我们还可以提供take(_: Range<Int>)替代方案:

public extension CollectionType where Index.Distance == Int {

    public func take(range: Range<Int>) -> SubSequence {
        let from = range.startIndex <= 0 ? startIndex : startIndex.advancedBy(range.startIndex, limit: endIndex)
        let to = range.endIndex <= 0 ? startIndex : startIndex.advancedBy(range.endIndex, limit: endIndex)
        return self[from..<to]
    }
}

s.characters.take(3...5).array // ["d", "o", "m"]

答案 5 :(得分:0)

与可选集合相比,您应该更喜欢空集合。快速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])
    }
}