1

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 " = "

Hassan Taleb
  • 2,350
  • 2
  • 19
  • 24
  • 2
    To answer your exact question, you can use `RangeSet`, which is introduced in [SE-0270 Add Collection Operations on Noncontiguous Elements](https://github.com/apple/swift-evolution/blob/main/proposals/0270-rangeset-and-collection-operations.md). It's currently available as a [standalone package](https://github.com/apple/swift-se0270-range-set/), or as part of the [Standard Library Preview](https://github.com/apple/swift-standard-library-preview) – Alexander Mar 02 '23 at 19:27
  • ... however, it looks like you're trying to parse Swift code. This is incredibly complex, and you're doomed to likely get it wrong. Use [SwiftSyntax](https://github.com/apple/swift-syntax), the official library for parsing and manipulating Swift code. – Alexander Mar 02 '23 at 19:28

1 Answers1

1

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 ", " = "]

HangarRash
  • 7,314
  • 5
  • 5
  • 32