inorganik
inorganik

Reputation: 25525

Why does swift substring with range require a special type of Range

Consider this function to build a string of random characters:

func makeToken(length: Int) -> String {
    let chars: String = "abcdefghijklmnopqrstuvwxyz0123456789!?@#$%ABCDEFGHIJKLMNOPQRSTUVWXYZ"

    var result: String = ""
    for _ in 0..<length {
        let idx =  Int(arc4random_uniform(UInt32(chars.characters.count)))
        let idxEnd = idx + 1
        let range: Range = idx..<idxEnd
        let char = chars.substring(with: range)
        result += char
    }
    return result
}

This throws an error on the substring method:

Cannot convert value of type 'Range<Int>' to expected argument
type 'Range<String.Index>' (aka 'Range<String.CharacterView.Index>')

I'm confused why I can't simply provide a Range with 2 integers, and why it's making me go the roundabout way of making a Range<String.Index>.

So I have to change the Range creation to this very over-complicated way:

let idx =  Int(arc4random_uniform(UInt32(chars.characters.count)))
let start = chars.index(chars.startIndex, offsetBy: idx)
let end = chars.index(chars.startIndex, offsetBy: idx + 1)
let range: Range = start..<end

Why isn't it good enough for Swift for me to simply create a range with 2 integers and the half-open range operator? (..<)

Quite the contrast to "swift", in javascript I can simply do chars.substr(idx, 1)

Upvotes: 2

Views: 288

Answers (2)

Paulo Mattos
Paulo Mattos

Reputation: 19339

Swift takes great care to provide a fully Unicode-compliant, type-safe, String abstraction.

Indexing a given Character, in an arbitrary Unicode string, is far from a trivial task. Each Character is a sequence of one or more Unicode scalars that (when combined) produce a single human-readable character. In particular, hiding all this complexity behind a simple Int based indexing scheme might result in the wrong performance mental model for programmers.

Having said that, you can always convert your string to a Array<Character> once for easy (and fast!) indexing. For instance:

let chars: String = "abcdefghijklmnop"
var charsArray = Array(chars.characters)
...
let resultingString = String(charsArray)

Upvotes: 1

vacawama
vacawama

Reputation: 154603

I suggest converting your String to [Character] so that you can index it easily with Int:

func makeToken(length: Int) -> String {
    let chars = Array("abcdefghijklmnopqrstuvwxyz0123456789!?@#$%ABCDEFGHIJKLMNOPQRSTUVWXYZ".characters)

    var result = ""
    for _ in 0..<length {
        let idx = Int(arc4random_uniform(UInt32(chars.count)))
        result += String(chars[idx])
    }
    return result
}

Upvotes: 2

Related Questions