- 开发工具
【免费下载链接】swift-algorithms
Commonly used sequence and collection algorithms for Swift
rotate是 Swift Algorithms 库中一组原地(in-place)旋转集合元素的可变方法,能够以 O(n) 的时间复杂度把集合中任意位置的元素移到开头、并把被挤出的元素整体移到末尾,同时支持对任意子区间(subrange)进行旋转。读完本篇,你将掌握rotate(toStartAt:)与rotate(subrange:toStartAt:)的完整用法、返回值语义、复杂度特征,并理解其底层实现原理(通用 block-swap 算法与双向集合的三次反转优化)以及它在分治算法(如stablePartition)中的典型应用场景。
核心 API 与基本用法
rotate是一个修改集合自身内容的可变方法:它把"从指定索引开始的元素"移到整个集合的开头,其余元素依次向后"滚动"。官方指南给出的最小示例位于 Guides/Rotate.md:
var numbers = [10, 20, 30, 40, 50, 60] let p = numbers.rotate(toStartAt: 2) // numbers == [30, 40, 50, 60, 10, 20] // p == 4 -- numbers[p] == 10理解这段代码有两个关键点:
- 旋转结果:调用后
numbers变为[30, 40, 50, 60, 10, 20],即以索引2处的元素30为新的起点,之前被挤出的[10, 20]被整体搬到末尾。 - 返回值语义:
rotate返回的是旋转前位于开头(startIndex)的那个元素,在旋转后的新索引。这里旋转前第一个元素是10,旋转后它位于索引4,因此p == 4且numbers[p] == 10。这个返回值正是分治算法中"旧起点去了哪里"这一信息的来源。
从源码看,rotate(toStartAt:)在 Sources/Algorithms/Rotate.swift 中是对"全范围旋转"的便捷封装:
@inlinable @discardableResult public mutating func rotate(toStartAt newStart: Index) -> Index { rotate(subrange: startIndex..<endIndex, toStartAt: newStart) }注意它标注了@discardableResult,因此如果你不关心返回值,可以像普通语句一样直接调用而不会触发编译器警告。
子区间变体:绕过 CoW / slice 突变问题
文档特别强调:旋转在分治算法中的惯用场景是先切出 slice 再就地修改,而 Swift 的 Copy-on-Write(CoW)语义会让"通过 slice 修改数组"变得棘手——直接对arr[range]这类 slice 视图做旋转,容易产生不必要的拷贝或语义混乱。为此,rotate还提供了接受subrange:参数的变体,让你在原集合上精确指定要旋转的范围。
官方文档示例:
var numbers = [10, 20, 30, 40, 50, 60] numbers.rotate(subrange: 0..<3, toStartAt: 1) // numbers = [20, 30, 10, 40, 50, 60] numbers.rotate(subrange: 3..<6, toStartAt: 4) // numbers = [20, 30, 10, 50, 60, 40]第一个调用把子区间0..<3(即[10, 20, 30])以索引1为起点旋转成[20, 30, 10],范围之外的元素[40, 50, 60]完全不受影响;第二个调用对3..<6(即[40, 50, 60])以索引4为起点旋转成[50, 60, 40]。两次操作互不干扰,这正是子区间版本的价值所在:旋转被严格限定在给定范围内,range 之外的元素保持原相对位置不变。
详细设计:两个MutableCollection扩展方法
rotate以扩展MutableCollection的方式提供,这意味着所有支持按索引读写元素的可变集合(Array、ArraySlice、ContiguousArray以及自定义的MutableCollection类型)都能使用它。指南中给出了完整的设计签名:
extension MutableCollection { mutating func rotate(toStartAt p: Index) -> Index mutating func rotate( subrange: Range<Index>, toStartAt p: Index ) -> Index }对应的源码实现位于 Sources/Algorithms/Rotate.swift 与 Sources/Algorithms/Rotate.swift。两个方法共享同一套语义:
- 参数
p(源码中命名为newStart):旋转后应成为范围/集合起点的那一个元素的索引; - 参数
subrange:限定旋转发生的范围; - 返回值:旋转前处于范围起点的元素在旋转后的新索引。
MutableCollection上的rotate(toStartAt:)直接转发到全范围版本(Rotate.swift),而BidirectionalCollection上的同名方法(Rotate.swift)同样转发到对应的双向优化实现——后文会解释这两条实现路径的差异。
边界情况语义
从 Tests/SwiftAlgorithmsTests/RotateTests.swift 的测试可以总结出清晰的边界行为:
- 空子区间:
rotate(subrange: 3..<3, toStartAt: 3)不会改变任何元素顺序,返回值是原startIndex处的元素(testRotateEmptySubrange); - 空集合:
numbers.rotate(subrange: 0..<0, toStartAt: 0)返回numbers.startIndex,集合保持为空(testRotateSubrangeOnEmptyCollection); - 全范围旋转:
rotate(subrange: 0..<8, toStartAt: 1)等价于整体rotate(toStartAt: 1),把第一个元素搬到末尾(testRotateFullRange); - 幂等性:对任意长度
0...15、任意起点j,先rotate(toStartAt: j)再用返回值i做rotate(toStartAt: i)能精确还原原数组(testRotate),证明返回值确实指向"旧起点的新位置"。
复杂度
rotate是一个O(n)操作,其中n是所旋转范围的长度——无论旋转点选在哪里,遍历并重排全部元素的工作量上限都是线性的。
文档进一步指出:BidirectionalCollection版本能显著降低每个元素所需的交换(swap)次数,因此如果rotate未来被标准库采纳,它应当是一个MutableCollection的"定制点"(customization point),以便针对不同类型的集合选择最优实现。这一点在仓库中已经落地:MutableCollection与BidirectionalCollection各有一套独立的rotate(subrange:toStartAt:)实现,调度由 Swift 协议分发机制自动完成。
源码级原理:两条实现路径
rotate的实现位于 Sources/Algorithms/Rotate.swift,包含两种策略,理解它们有助于在自定义集合类型上预估性能。
通用MutableCollection路径:块交换(block-swap)算法
对任意MutableCollection(不一定双向可遍历),实现采用类似 C++ STLstd::rotate的"块交换"思路:把范围看成以m(即newStart)为分界的左右两段,反复交换较短那段的前缀,然后收缩问题规模并重复,直到两段都归位(Rotate.swift)。
核心辅助函数是_swapNonemptySubrangePrefixes(_:_:)(Rotate.swift):它从两个非空子区间的前端开始逐对swapAt,直到其中较短的区间先耗尽,并返回两个区间实际交换到的端点。主循环利用这个函数不断把元素搬运到最终位置,期间只做元素交换、不做临时缓冲分配:
let (s1, m1) = _swapNonemptySubrangePrefixes(s..<m, m..<e) if m1 == e { ... } // 左段情况:旧末尾元素已就位 s = s1 if s == m { m = m1 } // 收缩成更小的旋转子问题,继续实现注释里也说明,STL 为了省去一次比较而把循环拆成两段,这里没有刻意采用那类优化。
双向集合路径:三次反转(reverse)技巧
对于Self: BidirectionalCollection的类型,文档承诺"显著降低每个元素的交换次数"。其原理是经典的三段式反转法:把一个区间[s, e)以newStart为界分成[s, newStart)和[newStart, e),先各自反转,再把整个区间反转一遍,即可完成旋转。仓库实现(Rotate.swift)利用内部的reverse(subrange:)与_reverse(subrange:until:)完成三次局部反转:
reverse(subrange: subrange.lowerBound..<newStart) reverse(subrange: newStart..<subrange.upperBound) let (p, q) = _reverse(subrange: subrange, until: newStart) reverse(subrange: p..<q) return newStart == p ? q : p其中_reverse(subrange:until:)(Rotate.swift)是一个内部辅助方法:从两端相向交换元素,直到任意一端抵达limit,并返回尚未反转区间的上下界。这样即使旋转点不在区间正中,也只需对三段分别反转,每个元素最多交换一次,比通用路径省去大量重复的swapAt。测试testUnderscoreReverse与testReverse分别验证了这两个辅助方法的行为(见 RotateTests.swift)。
命名设计:为什么是toStartAt而不是shiftingToStart
指南在命名一节记录了 API 设计决策:索引参数过去曾被提议为shiftingToStart,但最终采用了toStartAt标签。理由是shiftingToStart会引入"shift(移位)"的概念,听起来像是"把那一个元素单独移到集合开头",而rotate的真实语义是"以该索引为新起点,整体旋转元素"。toStartAt更准确地表达了"从哪个元素开始"这一含义。
对于基于范围的变体,subrange:标签理论上可以省略(即写成numbers.rotate(0..<3, toStartAt: 2)),但保留标签是为了与其他基于范围的修改方法(如reverse(subrange:)、stablePartition(subrange:by:))保持一致的命名风格。
与其他语言的对比
C++:标准库<algorithm>中定义了std::rotate,其语义与本方法的rotate(subrange:toStartAt:)基本一致——都是把区间内从某个迭代器开始的元素移到区间开头,其余元素循环后移。仓库的实现思路也借鉴了 STL 的块交换策略(源码注释中直接提及 STL)。
Ruby:数组可通过一个位移量(正数向前、负数向后)旋转元素。对于零起始索引的集合,向前旋转 3 个元素等价于rotate(toStartAt: 3)。区别在于 Ruby 的Array#rotate返回新数组(非破坏性),而 Swift Algorithms 的rotate是原地修改的可变方法。
实战场景:分治算法中的旋转
指南明确指出,旋转的惯用场景是分治算法,而子区间变体正是为了绕开 CoW / slice 突变问题而设计。仓库内部就有活生生的例子:stablePartition的实现(Sources/Algorithms/Partition.swift)在递归地对左右两半完成稳定划分后,调用
return rotate(subrange: j..<k, toStartAt: i)把中间需要交换的两段元素一次性旋转归位,从而在保持稳定性的同时完成合并。这说明rotate并不仅仅是"把数组转一圈"的玩具 API,而是被本仓库自身用于构建更高级算法的底层基元。对同一主题的讨论还可参考 Sources/Algorithms/Documentation.docc/Partitioning.md 中与rotate相关的章节。
小结
rotate(toStartAt:)与rotate(subrange:toStartAt:)是MutableCollection上两个 O(n) 的原地旋转方法:前者旋转整个集合,后者只旋转指定子区间并以返回值报告"旧起点的当前位置"。实现上,通用MutableCollection路径采用 block-swap 算法,BidirectionalCollection路径则用三次反转把每个元素的交换次数降到最少。如果你正在编写需要就地重排元素的分治算法,或希望避免 slice + CoW 带来的坑,这两个方法就是标准答案。
- 开发工具
【免费下载链接】swift-algorithms
Commonly used sequence and collection algorithms for Swift
相关推荐
终极指南:使用Tachyons的rotate类实现惊艳的元素旋转与动画效果
终极指南:使用Tachyons的rotate类实现惊艳的元素旋转与动画效果 Tachyons是一个功能强大的CSS框架,它通过预定义的类来帮助开发者快速构建响应
前端MyTinySTL中的容器旋转:rotate与rotate_copy
MyTinySTL中的容器旋转:rotate与rotate_copy 你是否曾遇到需要将容器中元素重新排列的场景?比如将数组的前半部分移到末尾,或者在不使用额外
标准库Swift Algorithms变异算法深度探索:rotate和stablePartition的实现原理
Swift Algorithms变异算法深度探索:rotate和stablePartition的实现原理 Swift Algorithms库为Swift开发者提
开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考