Swift字符串识别十进制数字:兼容多分隔符与范围获取问题
Swift数字识别与范围映射问题解决
需求说明
从大字符串的单个token中识别开头的数字(整数或十进制数,支持.和,作为十进制分隔符),要求获取该数字在原大字符串中的准确范围,最终实现函数:
func scanQuantity(in string: String) -> (NSNumber, Range<String.Index>)?
现有代码问题
Scanner的scanDouble()方法仅支持系统默认的一种十进制分隔符,无法同时兼容点(.)和逗号(,);- 无法将token内部数字的范围,转换为其在原大字符串中的对应范围。
示例场景:处理大字符串"I'd like to have 4kg bananas"时,需识别token"4kg"内的数字"4"在原字符串中的范围,并提取后续单位"kg"。
现有代码
func detect(in string: String) { let matches = string.matches(of: pattern) for match in matches { let token = match.output.0 print("Scanning token: \(token)") if let quantity = scanQuantity(in: String(token), of: match.range) { print("Found quantity: \(quantity.value)") } } } private func scanQuantity(in token: String, of range: Range<String.Index>) -> Quantity? { let scanner = Scanner(string: token) #warning("this does not work for both comma and dot decimal separator") guard let quantity = scanner.scanDouble() else { return nil } if !scanner.isAtEnd { let unitSubstring = token[scanner.currentIndex...] print("Scanning for unit of quantity") print("Unit: \(unitSubstring)") #warning("how to get the range of the quantity in the whole string (relative to the range parameter)") return Quantity(value: NSNumber(value: quantity), range: range, unit: .piece) } else { return Quantity(value: NSNumber(value: quantity), range: range, unit: .piece) } } // 补充原代码未定义的Quantity结构体与Unit枚举 enum Unit { case piece // 可扩展其他单位类型 } struct Quantity { let value: NSNumber let range: Range<String.Index> let unit: Unit }
解决方案
1. 兼容两种分隔符的数字识别
放弃Scanner.scanDouble(),改用正则表达式匹配token开头的数字模式,支持点和逗号作为分隔符。匹配模式为^(\d+([.,]\d+)?|\d*[.,]\d+):
\d+:匹配整数部分[.,]\d+:匹配小数部分(点或逗号开头,后跟数字)- 两种组合覆盖整数、带小数点/逗号的小数场景
匹配到数字字符串后,将逗号替换为点,再转换为Double,最后封装成NSNumber。
2. 映射token内数字范围到原大字符串
已知token在原字符串中的range,步骤如下:
- 获取token内数字的起始和结束索引(相对于token本身);
- 计算数字在token中的偏移量;
- 将偏移量加到原token的起始索引上,得到原字符串中数字的
Range<String.Index>。
修正后的完整代码
import Foundation enum Unit { case piece // 可根据需求扩展更多单位,比如kg、g等 } struct Quantity { let value: NSNumber let range: Range<String.Index> let unit: Unit } func detect(in string: String) { // 匹配含数字+可能单位的token的正则 let pattern = #"\b\d+[.,]?\d*[a-zA-Z]*\b"# let matches = string.matches(of: try! Regex(pattern)) for match in matches { let token = match.output print("Scanning token: \(token)") if let quantity = scanQuantity(in: String(token), of: match.range, in: string) { print("Found quantity: \(quantity.value), range: \(quantity.range), unit: \(quantity.unit)") // 可在这里添加单位匹配逻辑,替换.piece为实际识别的单位 } } } private func scanQuantity(in token: String, of tokenRange: Range<String.Index>, in originalString: String) -> Quantity? { // 匹配开头的数字(支持.和,作为分隔符) let numberPattern = #"^(\d+([.,]\d+)?|\d*[.,]\d+)"# guard let numberMatch = token.firstMatch(of: try! Regex(numberPattern)) else { return nil } // 处理数字字符串,统一转换为点分隔符后转Double let numberString = numberMatch.output.0.replacingOccurrences(of: ",", with: ".") guard let numberValue = Double(numberString) else { return nil } // 计算数字在原字符串中的范围 let tokenStartIndex = tokenRange.lowerBound let numberEndOffset = token.distance(from: token.startIndex, to: numberMatch.range.upperBound) let numberEndIndex = originalString.index(tokenStartIndex, offsetBy: numberEndOffset) let numberRange = tokenStartIndex..<numberEndIndex // 提取单位子串(如果有的话) var unit: Unit = .piece if numberMatch.range.upperBound < token.endIndex { let unitSubstring = token[numberMatch.range.upperBound...] print("Found unit substring: \(unitSubstring)") // 这里可添加单位匹配逻辑,比如判断unitSubstring是kg/g等,替换unit的值 } return Quantity(value: NSNumber(value: numberValue), range: numberRange, unit: unit) } // 测试示例 detect(in: "I'd like to have 4kg bananas, 2,5 apples and 3.2 oranges")
代码说明
- 正则匹配确保能识别所有开头的合法数字格式(整数、带点/逗号的小数);
- 通过索引偏移计算,精准映射数字在原大字符串中的范围;
- 预留了单位匹配的扩展位置,可根据实际需求添加单位识别逻辑。
内容的提问来源于stack exchange,提问作者dehlen
相关产品推荐
相关产品推荐

