Etan Etan - 1 month ago 7
Swift Question

Creating range from normal collection index and reversed collection index

(Simplified example of a bigger problem. Yes, I know that this specific dumbed down example can be solved in much easier ways.)

I have a collection containing some ASCII code points of which I'd like to remove leading and trailing spaces.

func foo<T: BidirectionalCollection>(_ buffer: T) -> String
where T.Iterator.Element == UInt8,
T.SubSequence.Iterator.Element == T.Iterator.Element
{
if let valueStart = buffer.index(where: { $0 != 0x20 /* SP */ }),
let valueEnd = buffer.reversed().index(where: { $0 != 0x20 /* SP */ })
{
return String(bytes: buffer[valueStart ... valueEnd], encoding: .utf8)!
} else {
return ""
}
}


However, I get this error:


error: binary operator '...' cannot be applied to operands of type 'T.Index' and 'ReversedIndex'


The documentation of
BidirectionalCollection
states:


If you need a reversed collection of the same type, you may be able to use the collection's sequence-based or collection-based initializer.


However, when I try embedding
buffer.reversed()
in a
T()
, I get this error:


error: 'T' cannot be constructed because it has no accessible initializers


Because, apparently, the initializers are defined somewhere else.

In the end, I don't need the whole reversed collection to be of a certain type. I just want to be able to build ranges from the original collection's indices and the corresponding reversed collection's indices.

What am I overlooking here?

Answer

In order to get the index for the underlying collection from a ReversedIndex, you can simply use its base property.

Although note that because of the way ReversedCollection is implemented, this will actually return the index above the given index in the base collection (as startIndex is mapped to endIndex – which is a 'past the end' index).

Therefore you can simply use a half-open range operator ..< in order to make this index the non-inclusive upper bound:

func foo<T: BidirectionalCollection>(_ buffer: T) -> String
    where T.Iterator.Element == UInt8,
    T.SubSequence.Iterator.Element == T.Iterator.Element
{
    if let valueStart = buffer.index(where: {$0 != 0x20}),
        let valueEnd = buffer.reversed().index(where: {$0 != 0x20})?.base
    {
        // goes without saying that this will crash if String(bytes:encoding:) returns nil.
        return String(bytes: buffer[valueStart ..< valueEnd], encoding: .utf8)!
    } else {
        return ""
    }
}

Or, in cases where you need to work with the index itself, you could use Optional's map(_:) method in conjunction with the collection's index(before:) method to get the index before:

let index = (buffer.reversed().index{ $0 != 0x20 }?.base).map{ buffer.index(before: $0) }
Comments