Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is the Swift compiler marking this as an error?

I have two ways of writing the same code, one of which seems to be disliked by the Swift compiler. Can you please explain why?

Context:

let guaranteedValue: String
let cursorPositionFromEnd: Int

Working code:

let stringFromEndUntilCursorPosition = String(guaranteedValue.reversed()[0..<cursorPositionFromEnd])

Non-working code:

let reversedOriginalString = guaranteedValue.reversed()
let stringFromEndUntilCursorPosition = String(reversedOriginalString[0..<cursorPositionFromEnd])

Compiler error message: "Cannot subscript a value of type ReversedCollection<String> with an index of type Range<Int>"

Other working attempt:

let reversedOriginalString = guaranteedValue.reversed()[0..< cursorPositionFromEnd]
let stringFromEndUntilCursorPosition = String(reversedOriginalString)

Basically the idea is you can only subscript a reversed range {but probably not only} if you add the index at a function return, but that does not work if you first reference the variable with a let or var and then try subscripting it.

I also understand that it would probably work in the "non-working code" if the Range would be String.Index type or whatever is the new fashion of doing it.

Can anyone explain why? Is this a bug in Swift's compiler which already has enough "twisted" string logic?

like image 670
Fawkes Avatar asked Nov 15 '18 14:11

Fawkes


1 Answers

There are several reversed() methods, as explained in What trouble could bring assining reversed() to a swift array?.

In your first code example,

let stringFromEndUntilCursorPosition = String(guaranteedValue.reversed()[0..<cursorPositionFromEnd])

the compiler infers from the context (i.e. the subscript) that the Sequence.reversed() method is needed, which returns an array. Arrays are indexed by integers, therefore the code compiles. This method has O(n) complexity because a new array with all elements is created.

In

let reversedOriginalString = guaranteedValue.reversed()

there is no such context, and the compiler chooses the Bidirectional.reversed() method. Compared to the above mentioned method, this one has O(1) complexity. It returns a ReversedCollection which has its own index type, therefore

let stringFromEndUntilCursorPosition = String(reversedOriginalString[0..<cursorPositionFromEnd])

produces the observed error message.

Possible solutions:

  • Provide context to enforce the array creation:

    let reversedOriginalString: [Character] = guaranteedValue.reversed()
    // Or: let reversedOriginalString: Array = guaranteedValue.reversed()
    // Or: let reversedOriginalString = guaranteedValue.reversed() as Array
    let stringFromEndUntilCursorPosition = String(reversedOriginalString[0..<cursorPositionFromEnd])
    

    This works, but has the disadvantage of creating a temporary array.

  • Do the proper index calculations on the reversed collection:

    let reversedOriginalString = guaranteedValue.reversed()
    let pos = reversedOriginalString.index(reversedOriginalString.startIndex, offsetBy: cursorPositionFromEnd)
    let stringFromEndUntilCursorPosition = String(reversedOriginalString[..<pos])
    

    This avoids the intermediate array, but is tedious to write.

  • Use the prefix(maxLength:) method of sequences:

    let reversedOriginalString = guaranteedValue.reversed()
    let stringFromEndUntilCursorPosition = String(reversedOriginalString.prefix(cursorPositionFromEnd))
    
like image 98
Martin R Avatar answered Sep 27 '22 00:09

Martin R