Mutating function for replacingOccurrences(of:with:)

Dear SE,

When I have a string where I want to replace occurrences of a string with another, I have to use the following:

var str = "Hello, playground"
str = str.replacingOccurrences(of: "Hello", with: "Goodbye")

Sometimes, I found this can get very repetitive, especially if my variable name is long and I need to do multiple replacements.

I found that many developers on StackOverflow, including myself, suggest that one should create an extension to mimic a mutating version of the function. This extension will also remove the overhead of copying the original string. I believe adding a mutating version of the function would be a great addition to the standard library.

extension String {
    mutating func replaceOccurrences(of target: String, with replacement: String, options: String.CompareOptions = [], locale: Locale? = nil) {
        var range: Range<String.Index>?
        repeat {
            range = self.range(of: target, options: options, range: range.map { $0.lowerBound..<self.endIndex }, locale: locale)
            if let range = range {
                self.replaceSubrange(range, with: replacement)
            }
        } while range != nil
    }
}

I would also be interested in your comments on allowing the target to include multiple strings to be substituted by a single replacement. This would be especially useful when I need to replace multiple targets with simply “”. Potentially removeOccurrences(of target: String).

5 Likes

Seems like a worthwhile thing to look into. There are plenty of mutating/non-mutating pairs on swift types and this one in particular is something I'm sure has bothered many people (myself included).

Note that this isn't a Swift standard library method, and it is only present if you import Foundation due to NSString bridging and this method. There is a big overhaul of String processing in Swift planned, including regular expressions, that will probably touch on this area. You can read about some of the ideas in @Michael_Ilseman's State of String: ABI, Performance, Ergonomics, and You!, including a link to @Chris_Lattner3's stated goal for Swift to be “better at string processing than Perl”.

5 Likes

This method actually exists already, but on NSMutableString.

@lancep is working on Subsequence-based find/replace, which would extend this functionality to all (perhaps-Bidirectional)Collections.

edit: Here's the pitch: Range based Collection mutations

2 Likes

Just an FYI. The proposed solution won't work in the standard library since it uses the Locale type from Foundation.
I see several options to make it work, but each has limitations. E.g., no Locale in stdlib atm or omitting the argument will affect the equality checking algorithm.

I come up with the below solution:

extension String {
    mutating func replaceOccurrences(of target: String, with replacement: String) {
        var range: Range<String.Index> = String.Index(utf16Offset: 0, in: self) ..< String.Index(utf16Offset: target.count, in: self)
        repeat {
            if self[range] == target {
                replaceSubrange(range, with: replacement)
            }
            range = Range<String.Index>.init(uncheckedBounds: (index(after: range.lowerBound), index(after: range.upperBound)))
        } while range.upperBound < endIndex
    }
}

But I'm afraid that using utf16Offset might cause some issues. If you have any improvement ideas, let me know :)