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?

Accepted Reply

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

  • @Claude31 what was not working was, nothing was replaced. But, after checking your code, i think the mistake might have been mine. My isCelsius parameter returned is always false, hence why i thought nothing was replaced.

  • I can confirm yours works too. I used it and checked.

Add a Comment

Replies

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

  • @Claude31 what was not working was, nothing was replaced. But, after checking your code, i think the mistake might have been mine. My isCelsius parameter returned is always false, hence why i thought nothing was replaced.

  • I can confirm yours works too. I used it and checked.

Add a Comment

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.

  • Hi @Claude31 it is solved now. my function works. The problem was my isCelsius parameter kept returning false, hence why I thought nothing was changed. But i think my conversion between farehnheit and celsius has issues regarding the roundOff() function. 75.2 returns 76, instead of 75.

  • If you simply use String(Int(value)), 75.2 will return "75".

Add a Comment