【问题标题】:Swift: Extract a Substring from a StringSwift:从字符串中提取子字符串
【发布时间】:2020-08-23 20:41:08
【问题描述】:

我有一个字符串,可以是以下任意一种:

tempString = "What? The ID: 54673 Over there"
tempString = "Jump... ID: 4561E how high"

基本上,我想从字符串中检索五个字符的 ID:。对于大多数语言,我可以通过查找 ID: 来执行此操作,将 4 添加到 ID: 的开头,然后取接下来的五个字符。

例如,在 Excel 中:

=MID("Jump... ID: 4561E how high",FIND("ID:","Jump... ID: 4561E how high")+4, 5)

将 = 4561E。

我们如何用 Swift 做到这一点?

【问题讨论】:

  • 请添加您自己的解决方法

标签: swift find substring


【解决方案1】:

在“ID:”之后从 [0-9] 或 [A-F] 中查找前 5 个字符的正则表达式方法(仅当不是这种情况时才使用十六进制字母,您可以使用 A-Z)。

if let range = string.range(of: "(?<=ID: )[0-9A-F]{5}", options: .regularExpression) {
    let id = string[range]  // 
    // if you need a String instead of a substring
    let stringID = String(string[range])
}

编辑/更新:

看看你的答案,看起来要求与你原来的帖子完全不同,要找到一个由 8 个十六进制字符后跟连字符然后是 4 个十六进制字符后跟连字符(3 次)的 IF 化合物,而不是你可以使用的 12 个十六进制字符以下正则表达式"([0-9a-f]{8}-)([0-9a-f]{4}-){3}([0-9a-f]{12})"


let dataString = """
{
  "@odata.context": "$metadata#GeoFences(Points())/$entity",
  "ID": "2b2a2abc-5962-4290-92b4-773025ffd50b",
  "Points": {
    "POINT_TYPE": "F",
    "POINT_NUM": 0,
    "LATITUDE": 32.92197686725423,
    "LONGITUDE": -117.04306932263532,
    "parent_ID": "2b2a2abc-5962-4290-92b4-773025ffd50b"
  },
  "GEOFENCE_NAME": "New Fence",
  "GEOFENCE_TYPE": "O",
  "PRIVACY": "X",
  "CENTER_LAT": 32.92043316309709,
  "CENTER_LONG": -117.04286922250975,
  "ZOOM_LAT": 0.006238797350533787,
  "ZOOM_LONG": 0.005345531926053582,
  "PATH_TOLERANCE": 5,
  "ENTRANCE_TOLERANCE": 5
}
"""

if let range = dataString.range(of: "([0-9a-f]{8}-)([0-9a-f]{4}-){3}([0-9a-f]{12})", options: .regularExpression) {
    let id = dataString[range]  // 4561E
    print("ID:", id)
    // if you need a String instead of a substring
    let stringID = String(dataString[range])
    print("stringID:", stringID)
}

这将打印出来

ID:2b2a2abc-5962-4290-92b4-773025ffd50b

字符串ID:2b2a2abc-5962-4290-92b4-773025ffd50b


请注意,您的代码将导致"2b2a2abc-5962-4290-92b4-773025ffd50


编辑/更新2:

考虑到您的字符串是 JSON,您可以简单地解码您的字符串 id:

struct Root: Codable {
    let id: String
    enum CodingKeys: String, CodingKey {
        case id = "ID"
    }
}
do {
    let id = try JSONDecoder().decode(Root.self, from: Data(dataString.utf8)).id
    print(id)  // "2b2a2abc-5962-4290-92b4-773025ffd50b"
} catch {
    print(error)
}

如果您需要解码所有数据:

struct Root: Codable {
    let odataContext, id: String
    let points: Points
    let geofenceName, geofenceType, privacy: String
    let centerLat, centerLong, zoomLat, zoomLong: Double
    let pathTolerance, entranceTolerance: Int

    enum CodingKeys: String, CodingKey {
        case odataContext = "@odata.context", id = "ID", points = "Points", geofenceName = "GEOFENCE_NAME", geofenceType = "GEOFENCE_TYPE", privacy = "PRIVACY", centerLat = "CENTER_LAT", centerLong = "CENTER_LONG", zoomLat = "ZOOM_LAT", zoomLong = "ZOOM_LONG", pathTolerance = "PATH_TOLERANCE", entranceTolerance = "ENTRANCE_TOLERANCE"
    }
}

struct Points: Codable {
    let pointType: String
    let pointNum: Int
    let latitude, longitude: Double
    let parentID: String

    enum CodingKeys: String, CodingKey {
        case pointType = "POINT_TYPE", pointNum = "POINT_NUM", latitude = "LATITUDE", longitude = "LONGITUDE", parentID = "parent_ID"
    }
}

do {
    let root = try JSONDecoder().decode(Root.self, from: Data(dataString.utf8))
    print("ID:", root.id)  // ID: 2b2a2abc-5962-4290-92b4-773025ffd50b
    print("Root:", root)   // Root: Root(odataContext: "$metadata#GeoFences(Points())/$entity", id: "2b2a2abc-5962-4290-92b4-773025ffd50b", points: __lldb_expr_111.Points(pointType: "F", pointNum: 0, latitude: 32.92197686725423, longitude: -117.04306932263532, parentID: "2b2a2abc-5962-4290-92b4-773025ffd50b"), geofenceName: "New Fence", geofenceType: "O", privacy: "X", centerLat: 32.92043316309709, centerLong: -117.04286922250975, zoomLat: 0.0062387973505337885, zoomLong: 0.005345531926053582, pathTolerance: 5, entranceTolerance: 5)
} catch {
    print(error)
}

【讨论】:

  • 谢谢。我的代表太低,无法显示我的选票,但我正在努力。
  • 感谢您提供解码。我将更新代码以使用它。
【解决方案2】:

您可以在字符串上使用range(of:) 找到子字符串的range,然后使用该rangeupperBound 来索引原始字符串以获取从该点开始的子字符串。最后,prefix(5) 允许您获取该子字符串的前 5 个字符,String()Substring.Subsequence 转换回普通的String

if let range = tempString.range(of: "ID: ") {
    let ident = String(tempString[range.upperBound...].prefix(5))
    print(ident)
}

注意:range(of:)来自Foundation框架,所以需要import Foundationimport另一个使用Foundation的框架如@987654334 @ 或Cocoa

【讨论】:

  • 这就是 OP 所要求的。如果不需要范围 OP 也可以if let index = tempString.range(of: "ID: ")?.upperBound {
  • @LeoDabus,真的,我也考虑过这样写。
  • 哇。一个优雅的解决方案。比我的好多了。我不得不做一些转义: dataString.range(of: "ID\":\"") 谢谢。
  • @NYReno 你可以使用#"whatever"# 而不是转义你的字符串#"ID":""#
【解决方案3】:

对于你的情况可以使用 NSRegularExpression:

let tempString1 = "What? The ID: 54673 Over there"
let tempString2 = "Jump... ID: 4561E how high"

func matches(for regex: String, in text: String) -> [String] {
    do {
        let regex = try NSRegularExpression(pattern: regex)
        let results = regex.matches(in: text,
                                    range: NSRange(text.startIndex..., in: text))
        return results.map {
            String(text[Range($0.range, in: text)!])
        }
    } catch let error {
        print("invalid regex: \(error.localizedDescription)")
        return []
    }
}

var res1 = matches(for: "[0-9]+[A-Z]*", in: tempString1) // 54673
var res2 = matches(for: "[0-9]+[A-Z]*", in: tempString2) // 4561E

【讨论】:

  • 这个正则表达式非常宽松。它不关心数字是否以“ID:”开头,和/或它将匹配多少个数字或字母。
【解决方案4】:

好的,我开始这个练习是因为我懒得解码 JSON,因为我必须构建结构并让它们正确。所以,我想我会很快做一个子串。好吧,解码会更好、更快、更准确,但我将在下面展示我的代码。基本上,我想从 JSON 字符串中获取 ID:

{
  "@odata.context": "$metadata#GeoFences(Points())/$entity",
  "ID": "2b2a2abc-5962-4290-92b4-773025ffd50b",
  "Points": {
    "POINT_TYPE": "F",
    "POINT_NUM": 0,
    "LATITUDE": 32.92197686725423,
    "LONGITUDE": -117.04306932263532,
    "parent_ID": "2b2a2abc-5962-4290-92b4-773025ffd50b"
  },
  "GEOFENCE_NAME": "New Fence",
  "GEOFENCE_TYPE": "O",
  "PRIVACY": "X",
  "CENTER_LAT": 32.92043316309709,
  "CENTER_LONG": -117.04286922250975,
  "ZOOM_LAT": 0.006238797350533787,
  "ZOOM_LONG": 0.005345531926053582,
  "PATH_TOLERANCE": 5,
  "ENTRANCE_TOLERANCE": 5
}

我的代码如下。我将很快用解码替换它。变量 id 确实包含 id。

var foundI :Bool = false
var iIndex = 0
for (index, char) in dataString.enumerated() {
    if char == "I" {
        foundI = true
        iIndex = index
    } else if char == "D" {
        if foundI == true && index == iIndex+1 {
           var suffixStr =  String(dataString.dropFirst(iIndex+5))
           var id = String(suffixStr.prefix(36))
        
            print("New String \(id)")
        }
    }
}

【讨论】:

  • 查看我发布的正则表达式方法以查找 ID
  • 这将导致您帖子中的第二个字符串为“561E”
  • 是的,你是对的,因为我没有处理转义字符。谢谢。 vacawama: if let range = dataString.range(of: "ID\":\"") { let ident = String(dataString[range.upperBound...].prefix(36)) print(ident) }
猜你喜欢
  • 2011-07-21
  • 1970-01-01
  • 2018-09-29
  • 2021-12-23
  • 2012-02-28
  • 2011-11-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多