Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift: How to get substring from start to last index of character

Tags:

ios

swift

I want to learn the best/simplest way to turn a string into another string but with only a subset, starting at the beginning and going to the last index of a character.

For example, convert "www.stackoverflow.com" to "www.stackoverflow". What code snippet would do that, and being the most swift-like? (I hope this doesn't bring a debate, but I can't find good lesson on how to handle substrings in Swift.

like image 315
Jason Hocker Avatar asked Jan 28 '15 00:01

Jason Hocker


People also ask

How do you get the index of a character in a string in Swift?

To get index of a substring in a string with Swift 2: let text = "abc" if let range = text. rangeOfString("b") { var index: Int = text. startIndex.

How do I get the last character of a string in Swift?

Getting the last character To get the last character of a string, we can use the string. last property in Swift. Similarly, we can also use the suffix() method by passing 1 as an argument to it. The suffix() method can also be used to get the last n characters from a string.

How do I remove the first character from a string in Swift 5?

Swift String dropFirst() The dropFirst() method removes the first character of the string.

How do I get the first character of a string in Swift?

In Swift, the first property is used to return the first character of a string.


3 Answers

Just accessing backward

The best way is to use substringToIndex combined to the endIndexproperty and the advance global function.

var string1 = "www.stackoverflow.com"

var index1 = advance(string1.endIndex, -4)

var substring1 = string1.substringToIndex(index1)

Looking for a string starting from the back

Use rangeOfString and set options to .BackwardsSearch

var string2 = "www.stackoverflow.com"

var index2 = string2.rangeOfString(".", options: .BackwardsSearch)?.startIndex

var substring2 = string2.substringToIndex(index2!)

No extensions, pure idiomatic Swift

Swift 2.0

advance is now a part of Index and is called advancedBy. You do it like:

var string1 = "www.stackoverflow.com"

var index1 = string1.endIndex.advancedBy(-4)

var substring1 = string1.substringToIndex(index1)

Swift 3.0

You can't call advancedBy on a String because it has variable size elements. You have to use index(_, offsetBy:).

var string1 = "www.stackoverflow.com"

var index1 = string1.index(string1.endIndex, offsetBy: -4)

var substring1 = string1.substring(to: index1)

A lot of things have been renamed. The cases are written in camelCase, startIndex became lowerBound.

var string2 = "www.stackoverflow.com"

var index2 = string2.range(of: ".", options: .backwards)?.lowerBound

var substring2 = string2.substring(to: index2!)

Also, I wouldn't recommend force unwrapping index2. You can use optional binding or map. Personally, I prefer using map:

var substring3 = index2.map(string2.substring(to:))

Swift 4

The Swift 3 version is still valid but now you can now use subscripts with indexes ranges:

let string1 = "www.stackoverflow.com"

let index1 = string1.index(string1.endIndex, offsetBy: -4)

let substring1 = string1[..<index1]

The second approach remains unchanged:

let string2 = "www.stackoverflow.com"

let index2 = string2.range(of: ".", options: .backwards)?.lowerBound

let substring3 = index2.map(string2.substring(to:))
like image 171
fpg1503 Avatar answered Oct 18 '22 01:10

fpg1503


Swift 3, XCode 8

func lastIndexOfCharacter(_ c: Character) -> Int? {
    return range(of: String(c), options: .backwards)?.lowerBound.encodedOffset
}

Since advancedBy(Int) is gone since Swift 3 use String's method index(String.Index, Int). Check out this String extension with substring and friends:

public extension String {

    //right is the first encountered string after left
    func between(_ left: String, _ right: String) -> String? {
        guard let leftRange = range(of: left), let rightRange = range(of: right, options: .backwards)
        , leftRange.upperBound <= rightRange.lowerBound
            else { return nil }
    
        let sub = self.substring(from: leftRange.upperBound)
        let closestToLeftRange = sub.range(of: right)!
        return sub.substring(to: closestToLeftRange.lowerBound)
    }

    var length: Int {
        get {
            return self.characters.count
        }
    }

    func substring(to : Int) -> String {
        let toIndex = self.index(self.startIndex, offsetBy: to)
        return self.substring(to: toIndex)
    }

    func substring(from : Int) -> String {
        let fromIndex = self.index(self.startIndex, offsetBy: from)
        return self.substring(from: fromIndex)
    }

    func substring(_ r: Range<Int>) -> String {
        let fromIndex = self.index(self.startIndex, offsetBy: r.lowerBound)
        let toIndex = self.index(self.startIndex, offsetBy: r.upperBound)
        return self.substring(with: Range<String.Index>(uncheckedBounds: (lower: fromIndex, upper: toIndex)))
    }

    func character(_ at: Int) -> Character {
        return self[self.index(self.startIndex, offsetBy: at)]
    }

    func lastIndexOfCharacter(_ c: Character) -> Int? {
        guard let index = range(of: String(c), options: .backwards)?.lowerBound else
        { return nil }
        return distance(from: startIndex, to: index)
    }
}

UPDATED extension for Swift 5

public extension String {
    
    //right is the first encountered string after left
    func between(_ left: String, _ right: String) -> String? {
        guard
            let leftRange = range(of: left), let rightRange = range(of: right, options: .backwards)
            , leftRange.upperBound <= rightRange.lowerBound
            else { return nil }
        
        let sub = self[leftRange.upperBound...]
        let closestToLeftRange = sub.range(of: right)!            
        return String(sub[..<closestToLeftRange.lowerBound])
    }
    
    var length: Int {
        get {
            return self.count
        }
    }
    
    func substring(to : Int) -> String {
        let toIndex = self.index(self.startIndex, offsetBy: to)
        return String(self[...toIndex])
    }
    
    func substring(from : Int) -> String {
        let fromIndex = self.index(self.startIndex, offsetBy: from)
        return String(self[fromIndex...])
    }
    
    func substring(_ r: Range<Int>) -> String {
        let fromIndex = self.index(self.startIndex, offsetBy: r.lowerBound)
        let toIndex = self.index(self.startIndex, offsetBy: r.upperBound)
        let indexRange = Range<String.Index>(uncheckedBounds: (lower: fromIndex, upper: toIndex))
        return String(self[indexRange])
    }
    
    func character(_ at: Int) -> Character {
        return self[self.index(self.startIndex, offsetBy: at)]
    }
    
    func lastIndexOfCharacter(_ c: Character) -> Int? {
        guard let index = range(of: String(c), options: .backwards)?.lowerBound else
        { return nil }
        return distance(from: startIndex, to: index)
    }
}

Usage:

let text = "www.stackoverflow.com"
let at = text.character(3) // .
let range = text.substring(0..<3) // www
let from = text.substring(from: 4) // stackoverflow.com
let to = text.substring(to: 16) // www.stackoverflow
let between = text.between(".", ".") // stackoverflow
let substringToLastIndexOfChar = text.lastIndexOfCharacter(".") // 17

P.S. It's really odd that developers forced to deal with String.Index instead of plain Int. Why should we bother about internal String mechanics and not just have simple substring() methods?

like image 32
serg_zhd Avatar answered Oct 18 '22 01:10

serg_zhd


I would do it using a subscript (s[start..<end]):

Swift 3, 4, 5

let s = "www.stackoverflow.com"
let start = s.startIndex
let end = s.index(s.endIndex, offsetBy: -4)
let substring = s[start..<end] // www.stackoverflow
like image 22
Marián Černý Avatar answered Oct 18 '22 00:10

Marián Černý