www.robkerr.com
www.robkerr.com

mobile development, cloud computing and building great software

Rob Kerr
Author

Addicted to coding since writing my first programs for the Commodore computers in the 1980s. Currently working as an independent contractor focused on native iOS development.

Share


Tags


Twitter


www.robkerr.com

Swift 3.0 substrings made easy

Rob KerrRob Kerr

Swift is a fantastic, modern language, and has fast become my favorite. So much of what's built into it is intuitive, simple and makes coding much more expressive than older, more syntactically heavy programming languages.

But...sometimes its sophistication makes what was simple in older languages more complicated. Case in point is taking substrings. Substrings are not difficult to deal with in Swift, but personally I find the syntax confusing. Many others do as well, and it's common to address the confusion with an extension pattern.

Let's first look at some basics of how to take substrings "out of the box", and then look at a pretty common extension approach to make the substring syntax more approachable and simple.

The Range Construct

The foundation of taking substrings in Swift is using Range objects. Range is just what it sounds like -- an encapsulation of a beginning and ending index within a String.

Take a look at he following quick example. In the example, the .range function is used to get the start and end index of the word "quick" within the larger string. Then, if the range is not nil, that word is extracted using the range bounds, and printed to the console.

This is something we might do in real life, and the Swift syntax is expressive and simple to remember.

// find and return a substring using Swift 3.0
let sentence = "The quick brown fox jumped over the lazy dog."

if let quick = sentence.range(of: "quick") {
    let word = sentence[quick.lowerBound..<quick.upperBound]
    print(word) // prints "quick"
}

Substrings by known index

The Range syntax is used to extract strings by known index as well, but in this case the syntax really gets in the way and is not simple or easy to read.

C++ Substring baseline

Before getting into it, let's review how this would be done in C++:

// find and return a substring at known location using C++  
string sentence = "The quick brown fox jumped over the lazy dog.";

string word = sentence.Substring(4, 5);

cout << word;   // prints "quick"

The C++ is syntactically simple and easy to understand. Substring just starts at the fifth character, and extracts five characters. Simple.

Swift Substring equivalent

Swift is similar to the C++ standard library conceptually, but the use of the Range construction. This results in a significantly more verbose statement to accomplish what is simple to do in C++:

let sentence = "The quick brown fox jumped over the lazy dog."

let substringNoExtension = 
      sentence[sentence.index(sentence.startIndex, 
           offsetBy: 4)...sentence.index(sentence.startIndex, 
           offsetBy: 8)]

print(substringNoExtension)  // prints "quick"

The syntax is actually quite similar to the C++ version, but let's face it -- it's long and tedious. The interpretation is "extract from the string from index 4 to index 8 inclusive". OK, simple concept, but wow! Look at all that syntax. If you need to do this once, not a problem, but what if the code uses offsets frequently?

Swift using an Extension

Luckily, Swift has the concept of extensions which allow us to essentially append new methods and data to existing classes and structs -- even ones where we don't have the source code or aren't allowed to inherit new objects from them.

So first, let's add an extension that adds a subscript operator that accepts a closed range:

extension String {
    subscript(range: ClosedRange<Int>) -> String {
        let lowerIndex = index(startIndex, 
                 offsetBy: max(0,range.lowerBound), 
                 limitedBy: endIndex) ?? endIndex
        return substring(
            with: lowerIndex..<(index(lowerIndex, 
            offsetBy: range.upperBound - range.lowerBound + 1, 
            limitedBy: endIndex) ?? endIndex))
    }
}

OK, so yes, I know...this is a lot of code too. But remember, this is an extension, and you just need to add this extension method to global scope one time, then use it over and over wherever you need it.

Now with the extension added to global scope, the substring with known indexes becomes the following:

let sentence = "The quick brown fox jumped over the lazy dog."
let substringWithExtension = sentence[4...8]
print(substringWithExtension) // prints "quick"

This syntax is even simpler than the original C++, and it's immediately intuitive that we're taking a range of characters from the 4th to the 8th character.

Simple!

Full Extension Example

Below is a more complete extension to add a somewhat more complete substring extension to String which builds on the simple single example above.

extension String {
    subscript(i: Int) -> String {
        guard i >= 0 && i < characters.count else { return "" }
        return String(self[index(startIndex, offsetBy: i)])
    }
    subscript(range: Range<Int>) -> String {
        let lowerIndex = index(startIndex, offsetBy: max(0,range.lowerBound), limitedBy: endIndex) ?? endIndex
        return substring(with: lowerIndex..<(index(lowerIndex, offsetBy: range.upperBound - range.lowerBound, limitedBy: endIndex) ?? endIndex))
    }
    subscript(range: ClosedRange<Int>) -> String {
        let lowerIndex = index(startIndex, offsetBy: max(0,range.lowerBound), limitedBy: endIndex) ?? endIndex
        return substring(with: lowerIndex..<(index(lowerIndex, offsetBy: range.upperBound - range.lowerBound + 1, limitedBy: endIndex) ?? endIndex))
    }
}
Rob Kerr
Author

Rob Kerr

Addicted to coding since writing my first programs for the Commodore computers in the 1980s. Currently working as an independent contractor focused on native iOS development.

Comments