5

Wow. Swift makes it really fiddly to copy a substring from a simple String.

Most programming languages allow characters to be simply indexed by their integer position in the string, making targeting a character or a range a matter of simple maths. Because Swift allows a wide range of characters to be used with various bit depths, a precise (memory?) index for each character has to be found first, based its position from the start or end of the string. These positions can then be passed into a method of the String class that returns the substring in the range. I've written a function to do the work:

//arguments: The parent string, number of chars from 1st char in it and total char length of substring

func subStr(str: String, c1: Int, c2: Int) -> String {
    //get string indexes for range of substring based on args
    let ind1 = str.startIndex.advancedBy(c1)
    let ind2 = str.startIndex.advancedBy(c1+c2)
//calls substring function with a range object passed as an argument set to the index values
    let sub = str.substringWithRange(Range<String.Index>(start: ind1, end: ind2))
//substring returned
    return sub
}

The problem is that because the substringWithRange function only works with Range objects its not obvious how to check if the substring is out of bounds of the string. For example calling this function would produce an error:

subStr ("Stack Overflow", c1: 6, c2: 12)

The c1 argument is OK but the length of the (c2) substring exceeds the upper boundary of the parent string causing a fatal error.

How do I refine this function to make it handle bad arguments or otherwise refine this clunky process?

Many thanks for reading.

2 Answers 2

4

You can use

let ind1 = str.startIndex.advancedBy(c1, limit: str.endIndex)
let ind2 = str.startIndex.advancedBy(c1+c2, limit: str.endIndex)

to advance the start index by the given amounts, but not beyond the end index of the string. With that modification, your function gives

subStr ("Stack Overflow", c1: 6, c2: 12) // "Overflow"
subStr ("Stack Overflow", c1: 12, c2: 20) // ""
Sign up to request clarification or add additional context in comments.

Comments

4

I quickly made a nice substring function for Strings without Foundation dependency:

extension String {
    func substring(from: Int, length: Int) -> String {
        return String(dropFirst(from).prefix(length))
    }
}

If the length is bigger than possible, it just gives all the characters to the end. Can't crash Can't crash as long as neither argument is negative (it makes sense to crash if any argument is negative since that would be a major flaw in your code).

Example usage:

"Stack Overflow".substring(from: 6, length: 12)

gives

"Overflow"

4 Comments

"can't crash" — actually, this does crash if either argument is negative
How about using UInt as the parameter type given the param values cannot be negative?
@mz2 To be consistent with the rest of Swift I'd prefer Int, which also avoids needing casts all the time. Compare it to count returning an Int, or drop() taking an Int
Interesting. I had not internalised that as even an Apple promoted convention, but you're absolutely right. Here's a relevant question for others who may be equally ignorant as I on the topic: stackoverflow.com/questions/24180630/…

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.