swiftrangensrange

Subtract multiple ranges from one range


How can we subtract multiple ranges of type Range<String.Index> from one range of type Range<String.Index>?

Example:

let str = "let <#name#> = <#value#>"
let range = str.startIndex..<str.endIndex

//the range of str is {0, 24}
//the range of <#name#> is {4, 8}
//the range of <#value#> is {15, 9}

How can we subtract the ranges {4, 8} and {15, 9} from the range of str to obtain the ranges of "let " and " = "


Solution

  • Here's an extension to StringProtocol that will return an array of ranges from a string that are outside the passed in ranges:

    extension StringProtocol {
        func ranges(outsideOf ranges: [Range<String.Index>]) -> [Range<String.Index>] {
            var res = [Range<String.Index>]()
    
            var lastEnd = self.startIndex
    
            // Iterate each provided range (sorted by lowerBound)
            for range in ranges.sorted(by: { $0.lowerBound < $1.lowerBound }) {
                // Added the result if this range has space before the previous range
                if range.lowerBound > lastEnd {
                    res.append(Range(uncheckedBounds: (lastEnd, range.lowerBound)))
                }
                if range.upperBound > lastEnd {
                    lastEnd = range.upperBound
                }
            }
    
            // If there's any space after the last range, add that to the result
            if lastEnd < self.endIndex {
                res.append(Range(uncheckedBounds: (lastEnd, self.endIndex)))
            }
    
            return res
        }
    }
    

    Here's some sample code to demonstrate its use:

    let str = "let <@name@> = <@value@>"
    let nameRng = str.range(of: "<@name@>")!
    let valueRng = str.range(of: "<@value@>")!
    let ranges = str.ranges(outsideOf: [nameRng, valueRng])
    print(ranges.map { str[$0] })
    

    Output:

    ["let ", " = "]