【问题标题】:Separating a String from a CSV file从 CSV 文件中分离字符串
【发布时间】:2017-08-27 01:27:13
【问题描述】:

目前我让用户导入从ExcelNumbers 创建的CSV-file。工作表有多个字段,我可以毫无问题地导入这些字段。

但是,其中一个字段如下所示:

"[Title1: (100: S11), (100: S12), (100: 10), (100: 2), (100: 3)], [Title2: (300: 50), (300: 90), (300: 100a), (300: D), (300: E)], [Title3: (500: 2), (500: 112), (500: 6), (500: 110), (500: 113)]"

我应该如何将此字段分成如下所示的字典:

//[String: [[Int: String]]]

[
    "Title1": [[100: "S11"], [100: "S12"], [100:   "10"], [100:   "2"], [100:   "3"]],
    "Title2": [[300:  "50"], [300:  "90"], [300: "100a"], [300:   "D"], [300:   "E"]],
    "Title3": [[500:   "2"], [500: "112"], [500:    "6"], [500: "110"], [500: "113"]]
]

这个字段是由用户自己创建的,这似乎有点太复杂了,容易出错。也许更好的问题是如何更好地格式化而不是将该字段拆分到我想要的字典中?


也许是简化版:

//[String: [Int: [String]]]

[
    "Title1": [100: ["S11", "S12",   "10",   "2",   "3"]],
    "Title2": [300:  ["50",  "90", "100a",   "D",   "E"]],
    "Title3": [500:   ["2", "112",    "6", "110", "113"]]
]

关于这个字段的一些解释:

标题:可以是任何东西,它是嵌套字典的标题

嵌套字典中的key可以是100, 200, 300, 400, 500 or 600

嵌套字典中的值是String,与它们在嵌套字典中的键一起构成唯一键。

这意味着:

100: "S11" and 200: "S11" // valid
100: "S11" and 100: "S11" // not valid

extension String {

    // Separate alphanumeric characters
    func split(_ number: Int = 1) -> [String] {
        let number = max(number, 1)

        return self.components(separatedBy: CharacterSet.alphanumerics.inverted).filter { $0.characters.count > number - 1 }
    }
}

let string = "Appetizer: (100: S11), (100: S12), (100: 10), (100: 2), (100: 3) * Main: (300: 50), (300: 90), (300: 100a), (300: D), (300: E) * Dessert: (500: 2), (500: 112), (500: 6), (500: 110), (500: 113)"

let values = string.components(separatedBy: "*").map { $0.split() }

var result: [String: [Int: [String]]] = [:]

values.forEach {
    guard let key = $0.first else { return }

    result[key] = result[key] ?? [:]

    let values = $0.dropFirst()

    let name = values.enumerated().filter { $0.0 % 2 == 0 }.map { $0.1 }
    let code = values.enumerated().filter { $0.0 % 2 != 0 }.map { $0.1 }

    zip(name, code).forEach {
        guard let category = Int($0.0) else { return }

        result[key]?[category] = (result[key]?[category] ?? []) + [$0.1]
    }
}

这段代码产生了我想要的结果,但它看起来很丑陋,而且过于依赖值的位置(而不是检查值)。

关于如何使用更简单、更优雅的方式来做到这一点有什么建议吗?

【问题讨论】:

  • 您应该定义一个结构来保存这些数据。使用数组和字典会很快变得混乱。 (100: S11), (100: S12) 代表什么?
  • 我将创建一个结构(或者更确切地说是NSManagedObject,因为我必须将其保存在 CoreData 中)。 (100: S12) 代表(Int: String),其中100 代表类别,S12 代表标识符。
  • 我认为您应该认真重新考虑您的 Excel 模板。导出为纯文本已经够糟糕的了。嵌套更糟糕。如果类别名称中有空格,您的解决方案将不起作用,例如“今日特卖”
  • @CodeDifferent:这就是我害怕的原因。我让用户使用 Excel 导入的唯一原因是因为我至少需要一种让他们将菜单导入应用程序的方法。其他字段仅包含 1 个“字符串”,因此应该没问题(尽管我需要检查它们是否真正“唯一”。但是,此问题中的字段是创建一个“完整菜单”,其中包含可供选择的选项。在正常情况下他们不需要指定这个,但他们可以。另外我真的希望它保存在 1 个CSV 文件中,这样我也可以使用相同的格式导出。
  • 话虽如此,考虑到这些限制,我还能使用什么建议?另外我正在考虑使用NSRegularExpression 来实现结果,所以我可以将标题放在括号中,将项目放在括号中。

标签: swift string csv dictionary


【解决方案1】:

以下代码应该将您提供的数据解析为您想要的字典:

    let data = "[Title1: (100: S11), (100: S12), (100: 10), (100: 2), (100: 3)], [Title2: (300: 50), (300: 90), (300: 100a), (300: D), (300: E)], [Title3: (500: 2), (500: 112), (500: 6), (500: 110), (500: 113)]"

    let arr1 = data.components(separatedBy:"],")
    var dic = [String:[[String:Any]]]()
    for row1 in arr1 {
        let arr2 = row1.components(separatedBy:",")
        var key = ""
        for row2 in arr2 {
            var txt = row2.replacingOccurrences(of:" ", with:"")
            if txt.hasPrefix("[Title") {
                // First row
                txt = txt.replacingOccurrences(of:"[", with:"")
                let arr3 = txt.components(separatedBy:":")
                key = arr3[0]
                let key1 = arr3[1].replacingOccurrences(of:"(", with:"")
                let val1 = arr3[2].replacingOccurrences(of:")", with:"").replacingOccurrences(of:" ", with:"")
                dic[key] = [[key1:val1]]
            } else {
                let arr3 = row2.components(separatedBy:":")
                let key1 = arr3[0].replacingOccurrences(of:"(", with:"").replacingOccurrences(of:" ", with:"")
                let val1 = arr3[1].replacingOccurrences(of:")", with:"").replacingOccurrences(of:"]", with:"").replacingOccurrences(of:" ", with:"")
                dic[key]?.append([key1:val1])
            }
        }
    }
    print(dic)

以上回答了您的第一个问题,您如何将您提供的数据解析为您指定的字典。第二个问题有点复杂,因为我对数据的了解不够,无法提供有用的答案。但也许现在没有必要了,因为可以根据需要将数据拆分成字典? :)

【讨论】:

  • 感谢您的建议。这确实得到了我想要的结果。仍然感觉我的数据源很容易出错。也许我应该尝试找到一种更好的方法来创建源代码。
  • 这将是只有您知道如何最好地处理事情的事情,因为我们不会拥有所有信息。如果我的回答有帮助,请将其标记为“正确”。如果您正在寻找一种不同的方法并且这个问题不相关,那么您可能想要删除它?
  • 如果您不介意,请让这个问题多留一会。我已经提供了有关来源的更多信息,所以也许它会对这个问题有所帮助。如果没有,我将其标记为已回答。
  • 这是您的问题,请随意做任何适合您的事情 :) 但是从我的角度来看,尝试回答有关如何设置数据源的问题,我会需要知道来源是什么以及它如何将数据提供给您的应用程序?它是您控制的 API 端点吗?如果是,是否可以提供 JSON 格式的数据?如果源不在您的控制范围内,那么当您说必须找到更好的方法来创建源时,您实际上在寻找什么?
  • 嗯,源来自 Excel(或 Numbers)。假设用户能够在 Excel 中输入信息并将其导出到CSV,以便可以将其导入应用程序。所以简而言之,用户可以决定他想要使用的任何格式(现在),但我想让用户尽可能简单,如果你问我,它已经太复杂了:p。
【解决方案2】:

我假设您选择了第一种导出格式,即

[Title1: (100: S11), (100: S12), (100: 10), (100: 2), (100: 3)], [Title2: (300: 50), (300: 90), (300: 100a), (300: D), (300: E)], [Title3: (500: 2), (500: 112), (500: 6), (500: 110), (500: 113)]

您可以使用 RegEx 来解析该字符串。下面的答案不处理错误。如果您的标题包含分号(:),它也将不起作用

extension String {
    subscript(_ range: NSRange) -> String {
        let startIndex = self.utf16.index(self.utf16.startIndex, offsetBy: range.location)
        let endIndex = self.utf16.index(startIndex, offsetBy: range.length)

        return String(describing: self.utf16[startIndex..<endIndex])
    }
}

let str = "[Title1: (100: S11), (100: S12), (100: 10), (100: 2), (100: 3)], [Title2: (300: 50), (300: 90), (300: 100a), (300: D), (300: E)], [Title3: (500: 2), (500: 112), (500: 6), (500: 110), (500: 113)]"
let regex1 = try! NSRegularExpression(pattern: "\\[(.+?): (.+?)\\]", options: [])
let regex2 = try! NSRegularExpression(pattern: "\\((\\d+?): (.+?)\\)", options: [])

var result = [String: [Int: [String]]]()

regex1.enumerateMatches(in: str, options: [], range: NSMakeRange(0, str.utf16.count)) { match1, _, _ in
    guard let match1 = match1 else { return }

    let title = str[match1.rangeAt(1)]
    let categoriesStr = str[match1.rangeAt(2)]
    var categories = [Int: [String]]()

    regex2.enumerateMatches(in: categoriesStr, options: [], range: NSMakeRange(0, categoriesStr.utf16.count)) { match2, _, _ in
        guard let match2 = match2 else { return }

        let number = Int(categoriesStr[match2.rangeAt(1)])!
        let identifier = categoriesStr[match2.rangeAt(2)]

        if categories[number] == nil {
            categories[number] = [String]()
        }
        categories[number]!.append(identifier)
    }

    result[title] = categories
}

print(result)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-01-12
    • 2017-01-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多