Replace substring using regex in a string

Hi all. I am not sure what is wrong with my current code. What I am looking to do is change all values of a string with a substring of "around #C"

where # is the temperature. I am converting between farenheit and celsius and i wish to get all substrings then change the correct value depending on the current setting.

So if i have a string where

"This is today. The temperature is around 14C. Tomorrow the temperature will be around 18C"

So the regex would get "around 14C" and "around 18C" and change the values to farenheit. Result would be something like

"This is today. The temperature is around -1F. Tomorrow the temperature will be around -4F"

This is my function.

func formatCorrectDetail(_ detail: String, _ isCelsius: Bool) -> String {
    let regex = try! NSRegularExpression(pattern: "around ((\\d+)" + (isCelsius ? "F" : "C") + ")", options: [])
    let matches = regex.matches(in: detail, options: [], range: NSRange(location: 0, length: detail.utf16.count))
    let formattedString = NSMutableString(string: detail)

    for match in matches {
      let range = match.range(at: 2)
      if let rangeSubstring = Range(range, in: detail),
        let temperature = Double(String(detail[rangeSubstring])) {
        let val = isCelsius ? NumberTool.far2cel(temperature) : NumberTool.cel2far(temperature)
        formattedString.replaceCharacters(in: range, with: val)
      }
    }

    if formattedString.length > 0 {
      return String(formattedString)
    }
    else {
      return detail
    }
  }

The NumberTool.far2cel() and NumberTool.cel2far() are my custom functions to convert between celsius and farehnheit.

Thoughts?

Answered by Claude31 in 747424022

What was not working ?

Problem is that once you replace the first match, the second position's is wrong. Just start from the end.

I wrote far2cel and cel2far, and that works:

let tempMsg = "This is today. The temperature is around 14C. Tomorrow the temperature will be around 18C"

func far2cel(_ val: Double) -> String {
    return String((val - 32) / 1.8) + "C"
}

func cel2far(_ val: Double) -> String {
    return String((val * 1.8) + 32) + "F"
}

func formatCorrectDetail(_ detail: String, toCelsius: Bool) -> String { // changed for toCelsius, more readable
    let regex = try! NSRegularExpression(pattern: "around ((\\d+)" + (toCelsius ? "F" : "C") + ")", options: [])
    let matches = regex.matches(in: detail, options: [], range: NSRange(location: 0, length: detail.utf16.count))
    let formattedString = NSMutableString(string: detail)

    let reversedMatches = matches.reversed()
    for match in reversedMatches { // <<-- You need to start at the end
      let range = match.range(at: 2)
      if let rangeSubstring = Range(range, in: detail),
        let temperature = Double(String(detail[rangeSubstring])) {
          let rangeForUnit = NSRange(location: range.upperBound, length: 1)  // First remove old unit
          formattedString.replaceCharacters(in: rangeForUnit, with: "")

          let val = toCelsius ? far2cel(temperature) : cel2far(temperature)
        formattedString.replaceCharacters(in: range, with: val)
      }
    }

    if formattedString.length > 0 {
      return String(formattedString)
    }
    else {
      return detail
    }
  }

let newTempMsg = formatCorrectDetail(tempMsg, toCelsius: false)
print(newTempMsg)

That gives: This is today. The temperature is around 57.2F. Tomorrow the temperature will be around 64.4F

Accepted Answer

What was not working ?

Problem is that once you replace the first match, the second position's is wrong. Just start from the end.

I wrote far2cel and cel2far, and that works:

let tempMsg = "This is today. The temperature is around 14C. Tomorrow the temperature will be around 18C"

func far2cel(_ val: Double) -> String {
    return String((val - 32) / 1.8) + "C"
}

func cel2far(_ val: Double) -> String {
    return String((val * 1.8) + 32) + "F"
}

func formatCorrectDetail(_ detail: String, toCelsius: Bool) -> String { // changed for toCelsius, more readable
    let regex = try! NSRegularExpression(pattern: "around ((\\d+)" + (toCelsius ? "F" : "C") + ")", options: [])
    let matches = regex.matches(in: detail, options: [], range: NSRange(location: 0, length: detail.utf16.count))
    let formattedString = NSMutableString(string: detail)

    let reversedMatches = matches.reversed()
    for match in reversedMatches { // <<-- You need to start at the end
      let range = match.range(at: 2)
      if let rangeSubstring = Range(range, in: detail),
        let temperature = Double(String(detail[rangeSubstring])) {
          let rangeForUnit = NSRange(location: range.upperBound, length: 1)  // First remove old unit
          formattedString.replaceCharacters(in: rangeForUnit, with: "")

          let val = toCelsius ? far2cel(temperature) : cel2far(temperature)
        formattedString.replaceCharacters(in: range, with: val)
      }
    }

    if formattedString.length > 0 {
      return String(formattedString)
    }
    else {
      return detail
    }
  }

let newTempMsg = formatCorrectDetail(tempMsg, toCelsius: false)
print(newTempMsg)

That gives: This is today. The temperature is around 57.2F. Tomorrow the temperature will be around 64.4F

Looks like my implementation seems off

static func roundOff(_ val: String, _ decimalPlace: Int) -> String {
    let decimalFormatter = createDecimalFormatter()
    decimalFormatter.minimumFractionDigits = decimalPlace
    decimalFormatter.maximumFractionDigits = decimalPlace

    let bigDecimal = NSDecimalNumber(string: val)
    return decimalFormatter.string(from: bigDecimal.rounding(accordingToBehavior: NSDecimalNumberHandler(roundingMode: .up, scale: Int16(decimalPlace), raiseOnExactness: false, raiseOnOverflow: false, raiseOnUnderflow: false, raiseOnDivideByZero: false)))!
  }

static func cel2far(_ celsius: Double) -> String {
    return roundOff(String(celsius * 9.0 / 5.0 + 32.0), 0)
  }

  static func far2cel(_ fahrenheit: Double) -> String {
    return roundOff(String((fahrenheit - 32.0) * 0.5555555555555556), 0)
  }

LOL

Is it solved now ? If not, could you explain what the problem is precisely ? If solved, don't forget to close the thread.

Note: I don't know if problem was in func, but there was an issue with replacements.

Replace substring using regex in a string
 
 
Q