ReversedCollection.subscript inconvenience

The docs for reversed() specifically call out its behavior and how to get the functionality you want. Is there something else you would expect in the documentation?

Furthermore, copying map and filter's precedent and creating a fresh array would make the interface less useful because not only does it not provide the big win of shared indices, it brings a risk of switching the index to be a type that can be reused as far as the type system is concerned but is definitely not shared, a likely cause of bugs. And it makes the task of going to the same location in the base collection even harder.

2 Likes

My case is fairly simple: I have an array of placemarks that I store in one order (to make insertions efficient) and display in a table in reverse order. I was going to use ReverseCollection for the latter, so I could do placemarks.reversed()[indexPath.row]. Of course, though, I can write [placemarks.count - 1 - indexPath.row] or extend Array with a new subscript that reverses (mirrors) the given index. And that is a better solution for this case, since you avoid creating reverse wrappers to begin with. I just thought this is only one of many possible scenarios where one would want to subscript a reverse collection.

That makes sense! For something that direct, I think I might add a convenience fromEnd(_:) method to make it easier.

As your needs grow more complex, however, that might be insufficient — if you need to subset or page your data in certain contexts, you might not be offsetting from the end, or might be using an ArraySlice instead of Array. This is where the consistency of Swift's collection protocols really starts to be beneficial.

Imagine that you've built a super-simple generic table view data source that is generic over a collection:

class TVDS<C: Collection>: NSObject, UITableViewDataSource {
    var data: C
    var cellBuilder: (C.Element) -> UITableViewCell

    func tableView(_: UITableView, numberOfRowsInSection _: Int) -> Int {
        return data.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let i = data.index(data.startIndex, offsetBy: indexPath.row)
        return cellBuilder(data[i])
     }
}

Inside the TVDS implementation, you're working with Collection methods, not anything Array- or Int-specific. For that reason, it doesn't matter to TVDS what kind of collection you pass:

TVDS(data: myData) { /* ... */ }
TVDS(data: myData.reversed()) { /* ... */ }
TVDS(data: myData.reversed().prefix(10)) { /* ... */ }
TVDS(data: myData.reversed().dropFirst(30).prefix(10)) { /* ... */ }
4 Likes

For what it's worth, I really like the way ReversedCollection and others are designed. I wrote a blog post about this where I used a potential implementation for lastIndex(of:) as an example:

extension String {
    func lastIndex2(of char: Character) -> String.Index? {
        guard let reversedIndex = reversed().firstIndex(of: char) else {
            return nil
        }
        return index(before: reversedIndex.base)
    }
}

(This in on String to make the example a bit simpler, but it could be on BidirectionalCollection.)

2 Likes