我相信现有答案可以进一步改进,因为代码库中的多个地方可能需要此功能(重复常见操作时的代码气味)。所以考虑添加我自己的实现,并说明我为什么考虑这种方法(效率是良好的 API 设计的重要组成部分,应尽可能首选 只要可读性不会受到太大影响)。除了使用类型本身的方法强制执行良好的面向对象设计之外,我认为协议扩展很棒,我们可以使现有的答案更加更敏捷。限制扩展非常棒,因为您不要创建不使用的代码。使代码更简洁和可扩展通常可以使维护更容易,但有权衡(简洁是我首先想到的)。
因此,您可以注意,如果您仅想使用 可重用性的扩展想法,但更喜欢上面引用的 contains 方法,则可以修改这个答案。我试图让这个答案灵活地用于不同的用途。
TL;DR
您可以使用更高效的算法(空间和时间)并使用具有通用约束的协议扩展使其可扩展:
extension Collection where Element: Numeric { // Constrain only to numerical collections i.e Int, CGFloat, Double and NSNumber
func isIndexValid(index: Index) -> Bool {
return self.endIndex > index && self.startIndex <= index
}
}
// Usage
let checkOne = digits.isIndexValid(index: index)
let checkTwo = [1,2,3].isIndexValid(index: 2)
深入研究
效率
@Manuel 的答案确实非常优雅,但它使用了额外的间接层(请参阅here)。 indices 属性就像 startIndex 和 endIndex 创建的引擎盖下的 CountableRange<Int>,没有这个问题的原因(空间复杂度略高,特别是如果 String 很长)。话虽如此,时间复杂度应该与 endIndex 和 startIndex 属性之间的直接比较大致相同,因为 N = 2 即使 contains(_:) 是 O(N) 对于 Collections (Ranges只有开始和结束索引的两个属性)。
为了获得最佳的空间和时间复杂度、更大的可扩展性和稍微长一点的代码,我建议使用以下代码:
extension Collection {
func isIndexValid(index: Index) -> Bool {
return self.endIndex > index && self.startIndex <= index
}
}
请注意我是如何使用startIndex 而不是0 - 这是为了支持ArraySlices 和其他SubSequence 类型。这是发布解决方案的另一个动机。
示例用法:
let check = digits.isIndexValid(index: index)
对于一般的Collections,很难在 Swift 中通过设计创建一个无效的Index,因为 Apple 已将 associatedtype Index 的初始化程序限制在 Collection 上 - 只能从现有的有效 @ 创建987654343@(如startIndex)。
话虽如此,对Arrays 使用原始Int 索引是很常见的,因为在很多情况下您需要检查随机Array 索引。因此,您可能希望将该方法限制为更少的结构...
限制方法范围
您会注意到此解决方案适用于所有 Collection 类型(可扩展性),但只有在您想限制特定应用程序的范围时(例如,如果您不想不想要添加的 String 方法,因为您不需要它)。
extension Array {
func isIndexValid(index: Index) -> Bool {
return self.endIndex > index && self.startIndex <= index
}
}
对于Arrays,您不需要明确使用Index 类型:
let check = [1,2,3].isIndexValid(index: 2)
您可以根据自己的用例随意调整此处的代码,还有许多其他类型的 Collections,例如LazyCollections。您还可以使用通用约束,例如:
extension Collection where Element: Numeric {
func isIndexValid(index: Index) -> Bool {
return self.endIndex > index && self.startIndex <= index
}
}
这将范围限制为NumericCollections,但您也可以相反地显式使用String。同样,最好将函数限制为您专门用于避免代码蠕变。
跨不同模块引用方法
编译器已经应用了多项优化来防止泛型成为一般问题,但是当从单独的模块调用代码时这些不适用。对于这样的情况,使用@inlinable 可以为您带来有趣的性能提升,但代价是增加了框架二进制文件的大小。一般来说,如果您真的想提高性能并希望将函数封装在单独的 Xcode 目标中以获得 良好的 SOC,您可以尝试:
extension Collection where Element: Numeric {
// Add this signature to the public header of the extensions module as well.
@inlinable public func isIndexValid(index: Index) -> Bool {
return self.endIndex > index && self.startIndex <= index
}
}
我可以推荐尝试模块化代码库结构,我认为这有助于确保项目中的单一职责(和SOLID)以进行常见操作。我们可以尝试按照here 的步骤进行操作,这就是我们可以使用此优化的地方(尽管要谨慎)。可以为该函数使用该属性,因为编译器操作每个调用站点只添加一行额外的代码,但它可以进一步提高性能,因为没有将方法添加到调用堆栈中(所以没有' t 需要被跟踪)。如果您需要最先进的速度,并且您不介意小的二进制大小增加,这将非常有用。 (-: 或者试试新的XCFrameworks (但要注意