Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating a CountableClosedRange<Character>

Tags:

swift

I am trying to extend Character to conform to Strideable in order to create a CountableClosedRange of Character types. In the end, I would like to have something like this which prints the whole alphabet:

("A"..."Z").forEach{
    print($0)
}

For the time being, I am using UnicodeScalar types to calculate the distance between two characters. Because a scalar isn't available from a Charactertype, I need to create a String from the Character, get the first scalar's value, and calculate the distance between them:

extension Character: Strideable {

    func distance(to other: Character) -> Character.Stride {
        return abs(String(self).unicodeScalars.first?.value - String(other).unicodeScalars.first!.value)
    }

    func advanced(by n: Character.Stride) -> Character {
        return Character(UnicodeScalar(String(self).unicodeScalars.first!.value + n))
    }

}

Even with this, I get the error that Character does not conform to protocol Strideable and _Strideable. The compiler does not appear to be picking up the Stride associated type which comes with Strideable:

public protocol Strideable : Comparable {

    /// A type that can represent the distance between two values of `Self`.
    associatedtype Stride : SignedNumber

    // ...

}

What am I missing?

like image 201
JAL Avatar asked Feb 05 '23 22:02

JAL


2 Answers

As already said, because a Character can be made up of multiple unicode scalars, you cannot accurately determine how many different valid character representations lie between two arbitrary characters, and is therefore not a good candidate for conformance to Stridable.

One approach to your problem of simply wanting to print out the alphabet is to conform UnicodeScalar, rather than Character, to Stridable – allowing you to work with characters that are represented by a single unicode code point, and advance them based on that code point.

extension UnicodeScalar : Strideable {

    public func distance(to other: UnicodeScalar) -> Int {
        return Int(other.value) - Int(self.value)
    }

    /// Returns a UnicodeScalar where the value is advanced by n.
    ///
    /// - precondition: self.value + n represents a valid unicode scalar.
    ///
    public func advanced(by n: Int) -> UnicodeScalar {
        let advancedValue = n + Int(self.value)
        guard let advancedScalar = UnicodeScalar(advancedValue) else {
            fatalError("\(String(advancedValue, radix: 16)) does not represent a valid unicode scalar value.")
        }
        return advancedScalar
    }
}

Now you can form a CountableClosedRange<UnicodeScalar>, and can freely convert each individual element to a Character or String if desired:

("A"..."Z").forEach {

    // You can freely convert scalar to a Character or String
    print($0, Character($0), String($0))
}

// Convert CountableClosedRange<UnicodeScalar> to [Character]
let alphabetCharacters = ("A"..."Z").map {Character($0)}
like image 107
Hamish Avatar answered Feb 08 '23 12:02

Hamish


This wouldn't work the way you'd expect it to even if you could make it work. How many characters do you believe are between "A" and "Z"? Without defining your encoding, this isn't meaningful. In fact, if you explore how Characters conform to Comparable, they act more like floats than like integers. For example:

"N" < "Ń" // true
"Ń" < "Ñ" // true
"Ń" < "O" // true

Between N and O are many modifications on N, possibly an unbounded number given Unicode's ability to compose characters. (This is the same if you wrap these in Character().)

like image 24
Rob Napier Avatar answered Feb 08 '23 13:02

Rob Napier