Home > Net >  Trying to make a Swift extension to replace a number of occurrences in a String
Trying to make a Swift extension to replace a number of occurrences in a String

Time:09-09

So I am trying to make a replacement function for the string class on Swift to conform to how it works on Python (string to be replaced, string to be replaced with, count to be replaced) and I found this extension by @rmaddy that does that, however when I add it to my XCode project I get the error:

Referencing initializer 'init(_:)' on 'Range' requires that 'String.Index' conform to 'Strideable'

//Developed by rmaddy @ https://stackoverflow.com/users/2303865/rmaddy

extension String {
    func replacingOccurrences<Target, Replacement>(of target: Target, with replacement: Replacement, count: Int, options: String.CompareOptions = [], range searchRange: Range<String.Index>? = nil) -> String where Target : StringProtocol, Replacement : StringProtocol {
        var matches = [Range<String.Index>]()
        
        //This is where I get the error
        var sRange = searchRange ?? Range(startIndex..<endIndex)
        
        
        while matches.count < count && !sRange.isEmpty {
            if let mRange = range(of: target, options: options, range: sRange, locale: nil) {
                matches.append(mRange)
                if options.contains(.backwards) {
                    sRange = Range(sRange.lowerBound..<mRange.lowerBound)
                } else {
                    sRange = Range(mRange.upperBound..<sRange.upperBound)
                }
            } else {
                break
            }
        }
        
        var res = self
        for range in matches.sorted(by: { $0.lowerBound > $1.lowerBound }) {
            res.replaceSubrange(range, with: replacement)
        }
        return res
    }
}

I've tried going through the Swift documentation but can't really find how to fix this, please help.

To be clear I want to replace a number of occurrences of a word within a text file (read as a script) but not all. I am not using the find and replace function because I want it to be dynamic and to be able to pick the replacement word based on an algorithm. Basically I'm making a thing that changes occurrences of "said" with "exclaimed" and other synonyms. The project is a paraphrasing engine for long text files, it works easy on Python but rewriting it on Swift is proving very challenging.

Thanks for your time, people.

CodePudding user response:

There is some other points that you can improve with this extension. You should extend StringProtocol instead of String to make it generic and allow you to use it with Substrings as well. Another important change is to return Self instead of String. Note that when extending the StringProtocol you need to constrain Self to RangeReplaceableCollection to allow you to use replaceSubrange method. I would also add the locale to your method signature and some syntax improvement as well:

extension StringProtocol where Self: RangeReplaceableCollection {
    func replacingOccurrences<Target: StringProtocol, Replacement: StringProtocol>(
        of target: Target,
        with replacement: Replacement,
        count: Int,
        options: String.CompareOptions = [],
        range searchRange: Range<Index>? = nil,
        locale: Locale? = nil
    ) -> Self {
        var ranges: [Range<Index>] = []
        var searchRange = searchRange ?? startIndex..<endIndex
        while ranges.count < count && !searchRange.isEmpty {
            if let range = self.range(
                of: target,
                options: options,
                range: searchRange,
                locale: locale
            ) {
                ranges.append(range)
                searchRange = options.contains(.backwards) ?
                    searchRange.lowerBound..<range.lowerBound :
                    range.upperBound..<searchRange.upperBound
            } else {
                break
            }
        }
        var collection = self
        for range in ranges.sorted(using: KeyPathComparator(\.lowerBound, order: .reverse)) {
            collection.replaceSubrange(range, with: replacement)
        }
        return collection
    }
}
  • Related