The Swift 3 documentation for CountableRange stated that
INTEGER INDEX AMBIGUITY
Because each element of a CountableRange instance is its own index, for the range (-99..<100) the element at index 0 is 0. This is an unexpected result for those accustomed to zero-based collection indices, who might expect the result to be -99.
In Swift 4, CountableRange is just a type alias
public typealias CountableRange<Bound: Strideable> = Range<Bound>
where Bound.Stride : SignedInteger
and it seems that the above doc comment got lost with this change. But one can still see from the source code at Range.swift#L189 that the indices of a Range (of Strideable elements) is the range itself
public var indices: Indices {
return self
}
public subscript(position: Index) -> Element {
// FIXME: swift-3-indexing-model: tests for the range check.
_debugPrecondition(self.contains(position), "Index out of range")
return position
}
Your method would also fail if used with an ArraySlice:
[ [1, 2, 3, 4].dropFirst(), [2, 3, 4]].transposed().forEach{ print($0) }
// Fatal error: Index out of bounds
Generally (I think) it is not safe to use the indices of one collection with any other collection (with the exception of slices, which share their indices with the originating collection).
The problem does not occur with arrays, because array indices are always zero based integers.
You can fix your algorithm by working with offsets relative to the initial position instead. The nested types can also be simplified a bit, since Element == Iterator.Element for sequences:
extension Collection where Element: RandomAccessCollection {
func transposed() -> [[Element.Element]] {
guard let firstRow = self.first else { return [] }
return (0..<firstRow.count).map { offset in
self.map { $0[$0.index($0.startIndex, offsetBy: offset)] }
}
}
}
(Of course this assumes – as your original code – that all “rows” have the same number of elements.)