25

Now that I've learned Swift (to a reasonable level) I'm trying to get to grips with the standard library, but in truth it's mainly ελληνικά to me!

So a specific question: I have an array of strings and I can call reverse() on it.

let arr = ["Mykonos", "Rhodes", "Naxos"].reverse()

Now naively I thought I'd get back a type of Array from this. (Ruby for example has a similar method that you pass an array and get back an array)

But arr is now actually of type

ReverseRandomAccessCollection<Array<String>>

which is actually a struct, which conforms to CollectionType:

public struct ReverseRandomAccessCollection<Base : CollectionType where Base.Index : RandomAccessIndexType> : _ReverseCollectionType

This means I can do this:

for item in arr {
  print(item)
}

but I can't do

print(arr[0])

Why is this designed to be this way?

Dictionaries in Swift also implement CollectionType, so I can do this:

let dict = ["greek" : "swift sometimes", "notgreek" : "ruby for this example"].reverse()

But dictionaries are not ordered like arrays, so why can I call reverse() on dicts?

Bonus points if anyone can point me in the direction of where I can read up and improve my Swift stdlib foo, Ευχαριστώ!

Brynjar
  • 1,252
  • 1
  • 11
  • 24

3 Answers3

32

It is an performance optimization for both time and memory. The ReverseRandomAccessCollection presents the elements of the original array in reverse order, without the need to create a new array and copying all elements (as long as the original array is not mutated).

You can access the reversed elements with subscripts:

let el0 = arr[arr.startIndex]
let el2 = arr[arr.startIndex.advancedBy(2)]

or

for i in arr.indices {
    print(arr[i])
}

You can also create an array explicitly with

let reversed = Array(["Mykonos", "Rhodes", "Naxos"].reversed())

A dictionary is also a sequence of Key/Value pairs. In

let dict = ["greek" : "swift sometimes", "notgreek" : "ruby for this example"].reverse()

a completely different reversed() method is called:

extension SequenceType {
    /// Return an `Array` containing the elements of `self` in reverse
    /// order.
    ///
    /// Complexity: O(N), where N is the length of `self`.
    @warn_unused_result
    public func reversed() -> [Self.Generator.Element]
}

The result is an array with the Key/Value pairs of the dictionary in reverse order. But this is of limited use because the order of the Key/Value pairs in a dictionary can be arbitrary.

Sabby
  • 2,586
  • 2
  • 27
  • 41
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • 1
    How one goes about learning this level of Swift knowledge still feels like Greek to me though ;) – Brynjar Jan 01 '16 at 18:46
  • It’s maybe worth pointing out the reason arrays use the lazy reverse whereas dictionary return a new array is because dictionary only has a forward index, which means there is no constant-time reversal. The best they can do is linear time. Whereas collections with bidirectional or random access indices pay no penalty for their lazy reversal. – Airspeed Velocity Jan 01 '16 at 20:04
  • (unlike `map` and `filter` for which you pay a re-calculation penalty each time you use them, hence you have to explicitly invoke laziness via `.lazy`) – Airspeed Velocity Jan 01 '16 at 20:05
  • @AirspeedVelocity Could you elaborate briefly on what is meant by 'dictionary only has a forward index'? – Brynjar Jan 04 '16 at 13:35
  • 2
    The kind of index a collection has determines how you can move over the collection. All collections have forward indexes – you can start at `startIndex`, and call `.successor()` until you reach `endIndex`, but you can't move an index backwards (including from the `endIndex` – so you can't start at the end and move back to the front). The next level up is a bidirectional index, which adds a `.predecessor()` method. There you can move backwards, so you can start at `endIndex` and move back. This is what allows `ReverseCollection` to wrap a collection and provide a reversed version of it lazily. – Airspeed Velocity Jan 05 '16 at 17:29
3

From the language docs of ReverseCollention (result of .reverse()):

The reverse() method is always lazy when applied to a collection with bidirectional indices, but does not implicitly confer laziness on algorithms applied to its result.

In other words, for ordinary collections c having bidirectional indices:

  • c.reverse() does not create new storage

...

Hence, you could see your ReverseRandomAccessCollection as a random access wrapper over your not yet reversed array (i.e., your original array arr has not yet been copied and reversed to a new position in memory).

Naturally, from the above, you can't index the reverse collection directly, as an Array gives access as a pointer to the memory that holds the array, and indexing corresponds to proceeding bitwise (depending on type) forward in memory. We can still, however, access the elements of the "reverse array" in array index style using ReverseRandomAccessIndex:

let arr = ["Mykonos", "Rhodes", "Naxos"]
let arrReverse = arr.reverse()
    /* ReverseRandomAccessCollection access "wrapper" over
       the 'arr' data in memory. No new storage allocated */

let myIndex = arrReverse.startIndex.advancedBy(2)
    /* bIndex type: 
       ReverseRandomAccessIndex<ReverseRandomAccessIndex<Index>> */

print(arrReverse[myIndex]) // "Mykonos"

Conversely, we can explicitly allocate memory for our reverse array, and treat it just as any other array. At this point, the arrReverse is a separate array than arr, and holds no relation to the former other than (once) being created by using it.

let arr = ["Mykonos", "Rhodes", "Naxos"]
let arrReverse = Array(arr.reverse())
    /* Array<String> */

let myIndex = arrReverse.startIndex.advancedBy(2)
    /* bIndex type: Int */

print(arrReverse[myIndex]) // "Mykonos"

Martin R beat me to it, so see his note regarding the dictionaries.

dfrib
  • 70,367
  • 12
  • 127
  • 192
  • Small correction: Since the array has a RandomAccessIndexType index, the result of reverse() is a http://swiftdoc.org/v2.0/type/ReverseRandomAccessCollection/. You would get a ReverseCollection e.g. when reversing the characters of a String. – Martin R Jan 01 '16 at 18:16
0

With Swift 3.0, you can directly access the Array value through index.

var streets = ["Albemarle", "Brandywine", "Chesapeake"]
streets = streets.reversed()
print("Array at index is \(streets[0])")

This will print "Chesapeake"

Sabby
  • 2,586
  • 2
  • 27
  • 41