Arrays 将csv导入到结构数组中

Arrays 将csv导入到结构数组中,arrays,swift,struct,Arrays,Swift,Struct,编辑:我需要将CSV(具有字符串和整数列类型)作为结构数组导入不是作为二维结构数组(答案很好地解释了这一点) 原始(解释不当)问题: 我正在尝试创建一个包含字符串和整数值的二维数组(基于我导入的CSV内容)。这些可以通过它们所在的“列”来识别。有没有办法做到这一点?我知道有三种可能的解决办法: 将数组声明为[Any],但我不希望这样做,因为我希望它是显式键入的 使用.flatmap(如中所示) let string=“123456789” 设intArray=string.components

编辑:我需要将CSV(具有字符串和整数列类型)作为结构数组导入不是作为二维结构数组(答案很好地解释了这一点)

原始(解释不当)问题:

我正在尝试创建一个包含字符串和整数值的二维数组(基于我导入的CSV内容)。这些可以通过它们所在的“列”来识别。有没有办法做到这一点?我知道有三种可能的解决办法:

  • 将数组声明为
    [Any]
    ,但我不希望这样做,因为我希望它是显式键入的

  • 使用
    .flatmap
    (如中所示)

    let string=“123456789” 设intArray=string.components(以“,”分隔)。flatMap{Int($0)} 但我不希望这样,因为我的数组的“String”列中可能有整数

  • 将我的数组保持为
    [String]
    ,并将我的“Int”列保持为字符串(这就是我当前正在做的事情)

  • 下面是我正在使用的代码的简化版本。当我试图将逗号分隔的字符串解析为具有不同值类型的数组时,它会出错

    非常感谢所有的帮助。。。谢谢大家!

    struct MyStruct {
        let Str1 : String
        let Str2 : String
        let Str3 : String
        let Str4 : String
        let Int1 : Int
    }
    
        func csvStringToArray(stringCSV: String) -> [[MyStruct]] {
            
            // create an empty 2-D array to hold the CSV data.
            var dataArray: [[MyStruct]] = []
            
            // parse the CSV into rows.
            var csvRows: [String] = stringCSV.components(separatedBy: "\n") 
           
            print(csvRows)
            // ["a,b,c,d,99", "j,k,l,m,98", "w,x,y,z,97", etc]
    
            // append each row (1-D array) to dataArray (filling the 2-D array).
            for i in csvRows {
                let csvColumns: [MyStruct] = i.components(separatedBy: ",") // Cannot convert value of type '[String]' to specified type '[MyStruct]'
                dataArray.append(csvColumns)
                print(csvColumns) // this is the output if I set csvColumns: [String]; otherwise it errors out.
                // ["a", "b", "c", "d", "99"]
                // ["j", "k", "l", "m", "98"]
                // ["w", "x", "y", "z", "97"]
                // etc...
            }
            print(dataArray) // again only if csvColumns: [String]
            // ["a", "b", "c", "d", "99"], ["j", "k", "l", "m", "98"], ["w", "x", "y", "z", "97"], [etc... ]
    
            return dataArray
        }
    

    您的
    MyStruct
    已经是一行的结构。
    MyStruct
    的每个元素都是一列。因此,最终您的
    数据数组
    (func csvStringToArray(stringCSV:String))的返回值应该是
    [MyStruct]
    ,而不是
    [[MyStruct]]
    。然后,您的函数变成:

    func csvStringToArray(stringCSV: String) -> [MyStruct] {
    
        // create an empty 2-D array to hold the CSV data.
        var dataArray: [MyStruct] = []
    
        // parse the CSV into rows.
        var csvRows: [String] = stringCSV.components(separatedBy: "\n") 
    
        print(csvRows)
        // ["a,b,c,d,99", "j,k,l,m,98", "w,x,y,z,97", etc]
    
        // append each row (1-D array) to dataArray (filling the 2-D array).
        for i in csvrows {
            let columns = i.components(separatedBy: ",")
            let csvColumns: MyStruct = MyStruct.init(Str1: columns[0], Str2: columns[1], Str3: columns[2], Str4: columns[3], Int1: 
            Int(columns[4])!)
            dataArray.append(csvColumns)
        }
        print(dataArray) 
        return dataArray
    }
    

    这显然是假设您的数据类型和结构总是有效的(您的行总是有4个字符串和一个整数,这样就可以对它们进行索引或强制展开),否则您将不得不为此添加检查。

    正如您在注释中指出的,这一行不起作用:

    let csvColumns: [MyStruct] = i.components(separatedBy: ",")
    
    组件(separatedBy:)
    方法始终返回字符串数组(
    [String]
    ),因此不能将该值设置为键入MyStruct数组的变量(
    [MyStruct]

    看起来您的MyStruct类型应该表示一整行数据(4个字符串和一个int),所以我认为说一列是MyStruct数组是不正确的。第一个原因是,这些不是列,而是行,看起来1个MyStruct==1行值,数据数组将只是这些MyStruct行值的数组(
    [MyStruct]
    ),而不是现在的MyStruct实例数组(
    [[MyStruct]]

    也就是说,为了最接近您所拥有的内容,我建议将MyStruct替换为可以保存字符串或Int的FieldValue枚举。使用这种方法,您的代码如下所示:

    enum FieldValue {
        case string(String)
        case int(Int)
    }
    
    func csvStringToArray(stringCSV: String) -> [[FieldValue]] {
    
        // create an empty 2-D array to hold the CSV data.
        var dataArray: [[FieldValue]] = []
    
        // parse the CSV into rows.
        var csvRows: [String] = stringCSV.components(separatedBy: "\n") 
    
        print(csvRows)
        // ["a,b,c,d,99", "j,k,l,m,98", "w,x,y,z,97", etc]
    
        // append each row (1-D array) to dataArray (filling the 2-D array).
        for i in csvRows {
            let csvRowValues: [FieldValue] = i.components(separatedBy: ",").map{
                if let value = Int($0) { 
                    return FieldValue.int(value)
                }
                return FieldValue.string(value)
            }
        dataArray.append(csvRowValues)
        print(csvRowValues)
        print(dataArray) 
        return dataArray
    }
    

    如果您希望转换为全类型,并避免
    Any
    ,请尝试以下方法:

    struct MyDataType {
    
      struct Row {
    
        enum DataPoint {
          case string(String)
          case integer(Int)
    
          init?(stringValue: String) {
            guard !stringValue.isEmpty else { return nil }
    
            if let integerValue = Int(stringValue) {
              self = .integer(integerValue)
            } else {
              self = .string(stringValue)
            }
          }
        }
    
        let dataPoints: [DataPoint]
    
        init?(csv: String) {
          let components = csv.components(separatedBy: ",")
          guard !components.isEmpty else { return nil }
    
          dataPoints = components.flatMap(DataPoint.init)
        }
      }
    
      let rows: [Row]
    
      init?(csv: String) {
        let csvRows = csv.components(separatedBy: .newlines)
        guard !csvRows.isEmpty else { return nil }
    
        rows = csvRows.flatMap(Row.init)
      }
    }
    
    
    // Example usage.
    
    let csvString = """
    123,134,a,65,4,bc
    43,53,t,4,5,1
    a,3,e,12,u,50
    """
    
    //  Force unrwapping here, because we know `csvString` is valid csv.
    //  You should not force unwrap generally.
    let data = MyDataType(csv: csvString)!
    
    if let firstRow = data.rows.first {
    
      print("First rows content:")
    
      for (idx, dataPoint) in firstRow.dataPoints.enumerated() {
        let descriptor: String
    
        switch dataPoint {
        case .string(let stringValue):
          descriptor = "String: \(stringValue)"
        case .integer(let integerValue):
          descriptor = "Integer: \(integerValue)"
        }
    
        print("[\(idx)] > \(descriptor)")
        /*
         Prints:
    
    
         First rows content:
         [0] > Integer: 123
         [1] > Integer: 134
         [2] > String: a
         [3] > Integer: 65
         [4] > Integer: 4
         [5] > String: bc
         */
      }
    }
    
    说明:

    • MyDataType
      表示所有导入的csv数据
    • 表示导入的csv数据中的一行
    • DataPoint
      可以保存
      字符串
      Int
      。请注意,这与
      Optional
      的工作原理非常相似
    • 每个
      MyDataType
      都有多个
      s
    • 每个
      都有多个
      数据点
      s
    • MyDataType
      Row
      DataPoint
      声明可以解析csv字符串各自部分的初始化器。添加了一些保护措施来检查空字符串,但这取决于实现

    使现代化 考虑到评论中的更多内容,您将如何扩展上述通用解决方案并强制实施特定的csv格式:

    extension MyDataType.Row {
      init?(enforcingCustomFormatOn csv: String) {
        let components = csv.components(separatedBy: ",")
        guard components.count == 5 else { return nil }
    
        dataPoints = components.enumerated().flatMap { idx, stringValue in
          switch idx {
            //  Enforcing .string in the first 4 columns
          case 0..<4: return .string(stringValue)
          default: return DataPoint(stringValue: stringValue)
          }
        }
      }
    }
    
    extension MyDataType {
      init?(enforcingCustomFormatOn csv: String) {
        let csvRows = csv.components(separatedBy: .newlines)
        guard !csvRows.isEmpty else { return nil }
    
        rows = csvRows.flatMap(Row.init(enforcingCustomFormatOn:))
      }
    }
    
    扩展名MyDataType.Row{
    初始化?(强制csv:String上的CustomFormat){
    让components=csv.components(以“,”分隔)
    guard components.count==5 else{return nil}
    dataPoints=components.enumerated().flatMap{idx,中的stringValue
    交换机idx{
    //在前4列中强制执行.string
    
    案例0.为什么不使用CSV解析库?首先,
    组件(以“,”分隔)
    将失败,因为检测到所有转义的逗号(必须作为字符串的一部分进行解析,而不是CSV结构的一部分)。为了简单起见,我试图避免使用第三方库。我知道我的数据将始终遵循相同的格式(我肯定还有其他问题,但你提到的具体问题不会影响我)。即使在这种情况下,使用一个也会更好吗?我认为用已经免费提供的东西来改造轮子并不是一件简单的事。需求改变,数据格式改变,所以我不会浪费时间为如此有限的CSV子集编写解析器。值得一提的是。进一步反思,我喜欢
    组件(以“,”分隔)
    因为我可以很容易地看到它是如何工作的。以前没有使用过库,这对我来说更像是一个黑匣子。这是我需要加快速度的许多事情之一。那么,这肯定是需要解决的问题。黑匣子很好。你越频繁地生成功能良好的代码,而不必担心自己的问题h其内部结构越好。越少越好。是的。改变了这一点。谢谢:)谢谢你,卡布斯。尽管我这次使用了桑托什的方法,但详细的解释非常有用(我知道我的数据将始终遵循非常严格的格式)。你的方法将转换任何一行的整数(不仅仅是第五列),对吗?我需要将第5列中不包含的任何内容保留为字符串,即使它看起来像一个整数。感谢您指出,对于函数的返回值,我只需要在MyStruct周围使用一组括号,因为它已经设置为容纳元素的一维。这使得错误消息在