Article Image
read

switchboard The more I am doing in Swift, the more I am finding pattern matching with the switch statement useful. However, I have been dissappointed in it's lack of support for Regular Expressions. Recently, I ran into a case where I needed to derive some logical meaning from the format of a string. So, I started looking for an elegant way to codify a series of mutually exclusive pattern matches.

I ran across this post from NSHipster. By combining their Regex struct with the Swift pattern matching operator, I was able to write the following:

switch(someinputstring){
    case Regex(pattern: "^4[0-9]{12}(?:[0-9]{3})?$"):
        println("It's a Visa card")
    case Regex(pattern: "^3[47][0-9]{13}$"):
        println("It's American Express")
    case Regex(pattern: "^5[1-5][0-9]{14}$"):
        println("It's a MasterCard")
    case Regex(pattern: "^6(?:011|5[0-9]{2})[0-9]{12}$"):
        println("It's a Discover card")
    default:
        println("ERROR: Unknown card type")
}

Here's How

First, create a structure to wrap up and simplify NSRegularExpression:

//from NSHipster - http://nshipster.com/swift-literal-convertible/
struct Regex {
    let pattern: String
    let options: NSRegularExpressionOptions!

    private var matcher: NSRegularExpression {
        return NSRegularExpression(pattern: self.pattern, options: self.options, error: nil)
    }

    init(pattern: String, options: NSRegularExpressionOptions = nil) {
        self.pattern = pattern
        self.options = options
    }

    func match(string: String, options: NSMatchingOptions = nil) -> Bool {
        return self.matcher.numberOfMatchesInString(string, options: options, range: NSMakeRange(0, string.utf16Count)) != 0
    }
}

Then add a simple protocol for matching regular expressions and extend string to implement it.

protocol RegularExpressionMatchable {
    func match(regex: Regex) -> Bool
}

extension String: RegularExpressionMatchable {
    func match(regex: Regex) -> Bool {
        return regex.match(self)
    }
}

Finally, overload the Swift pattern matching operator to handle a Regex and a RegularExpressionsMatchable.

func ~=<T: RegularExpressionMatchable>(pattern: Regex, matchable: T) -> Bool {
    return matchable.match(pattern)
}

With these pieces in place, you can write switch statements like:

switch(someinputstring){
    case Regex(pattern: "^4[0-9]{12}(?:[0-9]{3})?$"):
        println("It's a Visa card")
    case Regex(pattern: "^3[47][0-9]{13}$"):
        println("It's American Express")
    case Regex(pattern: "^5[1-5][0-9]{14}$"):
        println("It's a MasterCard")
    case Regex(pattern: "^6(?:011|5[0-9]{2})[0-9]{12}$"):
        println("It's a Discover card")
    default:
        println("ERROR: Unknown card type")
}

Or,

switch(inputstring){
    case Regex("^clemson(\\su.+)?$", options: NSRegularExpressionOptions.CaseInsensitive): 
        println("GREAT School!!  Awesome Team!")
    case Regex("^(u.+)?south carolina$", options: NSRegularExpressionOptions.CaseInsensitive):
        println("I'm sorry.")
    default:
        println("Bless your heart.")
}
Blog Logo

Les Stroud


Published

Image

technically interesting...


Back to Overview