2

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)

1
  • 2
    Because characters aren't that simple. They don't all take the same amount of memory. See the Counting Characters section in the documentation for Strings. Commented Jul 6, 2017 at 23:44

2 Answers 2

2

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 } 
Sign up to request clarification or add additional context in comments.

2 Comments

I think you missed the .characters I have at the end of the string literal. In Swift 4, you will be able to drop the .characters. I tested this in Xcode and on Linux before posting.
@inorganik BTW, using just Array("abc") will work fine in Swift 4 given the improved String class ;)
1

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) 

3 Comments

Why does Swift even need the complexity of accounting for scalar sequences? Why would I ever care about that? Personally I prefer the way javascript does it - it hides the scalar mess because I'll never need to touch it, nor will 99.9% of us
@inorganik They try very hard to be Unicode correct, future-proof, by default. Actually, Swift is the one hiding the scalar "mess" from us ;) To see what I mean, pls check out this WWDC 2017 video around the 28:00 mark. They provide some nice examples of why this matters in practice...
Thanks Paulo I will check it out.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.