swiftstringunicoderangecharacter

Initialize a String from a range of Characters in Swift


In our code, we found a bug from not writing the alphabet correctly. Instead of "0123456789abcdefghijklmnopqrstuvwxyz", we had "0123456789abcdefghijklmnoqprstuvwxyz". So we are wondering if it's possible to avoid similar typo by declaring Strings made from ranges of characters?

Using Swift 4.1+, we tried:

attempt 1

let 📚1: String = "0"..."9" + "a"..."z"

Adjacent operators are in non-associative precedence group 'RangeFormationPrecedence'

attempt 2

let 📚2: String = ("0"..."9") + ("a"..."z")

Binary operator '+' cannot be applied to two 'ClosedRange<String>' operands

attempt 3

let 📚3: String = String("0"..."9") + String("a"..."z")

Cannot invoke initializer for type 'String' with an argument list of type '(ClosedRange<String>)'

attempt 4

let 📚4: String = (Character("0")...Character("9")) + (Character("a")...Character("z"))

Binary operator '+' cannot be applied to two 'ClosedRange<Character>' operands

attempt 5

let 📚5: String = String(Character("0")...Character("9")) + String(Character("a")...Character("z"))

Cannot invoke initializer for type 'String' with an argument list of type '(ClosedRange<Character>)'


Solution

  • "a"..."z" is a ClosedRange, but not a CountableClosedRange. It represents all strings s for which "a" <= s <= "z" according to the Unicode standard. That are not just the 26 lowercase letters from the english alphabet but many more, such as "ä", "è", "ô". (Compare also ClosedInterval<String> to [String] in Swift.)

    In particular, "a"..."z" is not a Sequence, and that is why String("a"..."z") does not work.

    What you can do is to create ranges of Unicode scalar values which are (UInt32) numbers (using the UInt32(_ v: Unicode.Scalar) initializer):

    let letters = UInt32("a") ... UInt32("z")
    let digits = UInt32("0") ... UInt32("9")
    

    and then create a string with all Unicode scalar values in those (countable!) ranges:

    let string = String(String.UnicodeScalarView(letters.compactMap(UnicodeScalar.init)))
        + String(String.UnicodeScalarView(digits.compactMap(UnicodeScalar.init)))
    
    print(string) // abcdefghijklmnopqrstuvwxyz0123456789
    

    (For Swift before 4.1, replace compactMap by flatMap.)

    This works also for non-ASCII characters. Example:

    let greekLetters = UInt32("α") ... UInt32("ω")
    let greekAlphabet = String(String.UnicodeScalarView(greekLetters.compactMap(UnicodeScalar.init)))
    print(greekAlphabet) // αβγδεζηθικλμνξοπρςστυφχψω