Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get components separated by regular expression, but also with separators?

Tags:

string

ios

swift

I have a string extension:

extension String {
    func split(usingRegex pattern: String) -> [String] {
        let regex = try! NSRegularExpression(pattern: pattern)
        let matches = regex.matches(in: self, range: NSRange(0..<utf16.count))
        let ranges = [startIndex..<startIndex] + matches.map{Range($0.range, in: self)!} + [endIndex..<endIndex]
        return (0...matches.count).map {String(self[ranges[$0].upperBound..<ranges[$0+1].lowerBound])}
    }
}

and I use it like this:

var string = "Hello45playground23today"
var output = string.split(usingRegex: "[0-9]+")

the output is:

["Hello", "playground", "today"]

But what I need is:

["Hello", "45", "playground", "23", "today"]

Is there a way to achieve that in Swift?

like image 548
Bartłomiej Semańczyk Avatar asked Jan 27 '23 00:01

Bartłomiej Semańczyk


1 Answers

Your code adds only the substrings between the matches (and before the first match and after the last match) to the result. What you need is also the substrings for the matches themselves. This can be done by creating an array with all indices where a match starts or ends, and then taking all substrings between consecutive indices:

extension String {
    func split(usingRegex pattern: String) -> [String] {
        let regex = try! NSRegularExpression(pattern: pattern)
        let matches = regex.matches(in: self, range: NSRange(startIndex..., in: self))
        let splits = [startIndex]
            + matches
                .map { Range($0.range, in: self)! }
                .flatMap { [ $0.lowerBound, $0.upperBound ] }
            + [endIndex]

        return zip(splits, splits.dropFirst())
            .map { String(self[$0 ..< $1])}
    }
}

Example:

let string = "Hello45playground23today"
let output = string.split(usingRegex: "[0-9]+")
print(output) // ["Hello", "45", "playground", "23", "today"]

The same can be done with an explicit loop (less sophisticated, but perhaps better readable):

extension String {
    func split(usingRegex pattern: String) -> [String] {
        let regex = try! NSRegularExpression(pattern: pattern)
        let matches = regex.matches(in: self, range: NSRange(startIndex..., in: self))
        var result: [String] = []
        var pos = startIndex
        for match in matches {
            let range = Range(match.range, in: self)!
            result.append(String(self[pos..<range.lowerBound]))
            result.append(String(self[range]))
            pos = range.upperBound
        }
        result.append(String(self[pos...]))
        return result
    }
}
like image 117
Martin R Avatar answered Jan 28 '23 15:01

Martin R