You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift字符串识别十进制数字:兼容多分隔符与范围获取问题

Swift数字识别与范围映射问题解决

需求说明

从大字符串的单个token中识别开头的数字(整数或十进制数,支持.和,作为十进制分隔符),要求获取该数字在原大字符串中的准确范围,最终实现函数:

func scanQuantity(in string: String) -> (NSNumber, Range<String.Index>)?

现有代码问题

  1. Scanner的scanDouble()方法仅支持系统默认的一种十进制分隔符,无法同时兼容点(.)和逗号(,);
  2. 无法将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,步骤如下:

  1. 获取token内数字的起始和结束索引(相对于token本身);
  2. 计算数字在token中的偏移量;
  3. 将偏移量加到原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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 19:09:30