如何实现同类型元素数组的双向无限循环序列(免逐个类型扩展)
实现任意数组的双向无限序列
我定义了如下协议:
protocol IndexConvertable{ associatedtype T static func convertAnyTypeToInt(_ type: Self) -> Int static func convertIntToAnyType(_ int: Int) -> Self }
该协议用于实现拥有双向无限取值的类型(比如Date)与Int的互转,示例如下:
extension Date: IndexConvertable{ typealias T = Date static func convertAnyTypeToInt(_ date: Date) -> Int { date.convertDateToInt() } static func convertIntToAnyType(_ int: Int) -> Date { int.convertIntToDate() } } extension Date{ /// 将Date转换为Int func convertDateToInt(){ ... } } extension Int{ /// 将Int转换为Date func convertIntToDate(){ ... } }
从逻辑上来说,任意元素类型相同的数组都可以通过循环映射转换为双向无限序列,比如:
示例1
let colors: [Color] = [.red, .blue, .purple] 目标序列 => [... .red, .blue, .purple, .red, .blue, .purple ...]
示例2
struct ColorView: View{ let color: Color var body: some View{ color.ignoreSafeArea() } } let colorViews: [ColorView] = [ ColorView(color: .red), ColorView(color: .blue), ColorView(color: .purple) ] 目标序列 => [... ColorView(color: .red), ColorView(color: .blue), ColorView(color: .purple), ColorView(color: .red), ColorView(color: .blue), ColorView(color: .purple) ...]
转换的计算方式如下(以[Color]为例):
| x | -5 | -4 | -3 | -2 | -1 | 0 | 1 | 2 | 3 | 4 | 5 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| c[y] | c[1] | c[2] | c[0] | c[1] | c[2] | c[0] | c[1] | c[2] | c[0] | c[1] | c[2] |
| 对应值 | .blue | .purple | .red | .blue | .purple | .red | .blue | .purple | .red | .blue | .purple |
let count = c.count // 计算目标索引y的公式 let y = { if x >= 0 { x % count } else { ((x % count) + count) % count } }()
这个公式适用于任意长度的数组。现在需要实现将任意同类型元素的数组转换为双向无限序列,但不想为每个数组元素类型编写扩展,请问该如何实现?或者有其他能达成相同目标的方法也可以。
解决方案
方法1:自定义随机访问集合实现双向无限序列
创建一个通用包装类型,遵循RandomAccessCollection协议,内部持有原数组,通过重写索引计算逻辑实现双向无限循环,支持正负索引直接访问:
struct InfiniteCycledCollection<Element>: RandomAccessCollection { typealias Index = Int private let base: [Element] init(base: [Element]) { precondition(!base.isEmpty, "原数组不能为空") self.base = base } // 无限序列无实际起止索引,用Int极值占位以符合协议要求 var startIndex: Index { Int.min } var endIndex: Index { Int.max } subscript(position: Index) -> Element { let count = base.count let adjustedIndex = position >= 0 ? position % count : ((position % count) + count) % count return base[adjustedIndex] } func index(after i: Index) -> Index { i + 1 } func index(before i: Index) -> Index { i - 1 } } // 给Array添加便捷转换方法 extension Array { func infiniteCycled() -> InfiniteCycledCollection<Element> { InfiniteCycledCollection(base: self) } }
使用示例:
let colors = [Color.red, .blue, .purple].infiniteCycled() print(colors[-5]) // 输出 .blue print(colors[0]) // 输出 .red print(colors[5]) // 输出 .purple
方法2:自定义序列生成器(支持双向迭代)
如果不需要随机访问特性,可创建双向迭代的无限序列生成器:
struct InfiniteCycleSequence<Element>: Sequence, IteratorProtocol { private let base: [Element] private var currentIndex: Int = 0 init(base: [Element]) { precondition(!base.isEmpty, "原数组不能为空") self.base = base } mutating func next() -> Element? { let element = base[currentIndex] currentIndex = (currentIndex + 1) % base.count return element } // 扩展反向迭代能力 func reversed() -> ReversedInfiniteCycleSequence<Element> { ReversedInfiniteCycleSequence(base: base) } } struct ReversedInfiniteCycleSequence<Element>: Sequence, IteratorProtocol { private let base: [Element] private var currentIndex: Int init(base: [Element]) { precondition(!base.isEmpty, "原数组不能为空") self.base = base self.currentIndex = base.count - 1 } mutating func next() -> Element? { let element = base[currentIndex] currentIndex = currentIndex > 0 ? currentIndex - 1 : base.count - 1 return element } } // 给Array添加便捷转换方法 extension Array { func infiniteCycle() -> InfiniteCycleSequence<Element> { InfiniteCycleSequence(base: self) } }
使用示例:
let colorViews = [ ColorView(color: .red), ColorView(color: .blue), ColorView(color: .purple) ].infiniteCycle() // 正向遍历前5个元素 var forwardIterator = colorViews.makeIterator() for _ in 0..<5 { print(forwardIterator.next()) } // 反向遍历前3个元素 var reversedIterator = colorViews.reversed().makeIterator() for _ in 0..<3 { print(reversedIterator.next()) }
方法3:利用标准库cycled()(仅正向无限)
如果只需要正向无限序列,可导入Swift的Algorithms包,直接使用标准库提供的cycled()方法,无需自定义类型:
import Algorithms let colors = [Color.red, .blue, .purple].cycled()
注意:该方法生成的序列不支持负索引访问,仅适用于正向无限遍历场景。
内容的提问来源于stack exchange,提问作者Harry CHOI
相关产品推荐
相关产品推荐

