Diclaimer:我是Swift的新手,我为我所做的任何明显的疏忽而道歉。
我正在尝试创建一个包含字符串和整数值的2D数组(基于我导入的CSV的内容)。这些可以通过"列来识别"他们在。有没有办法实现这一目标?我知道三种可能的解决方案:
1)将数组声明为[Any]
,但我不想这样做,因为我希望它是明确键入的
2)使用.flatmap
(如How to convert a String (numeric) in a Int array in Swift)
let string = "123,456,789"
let intArray = string.components(separatedBy: ",").flatMap { Int($0) }
但我不愿意,因为我可能在" String"中有整数。我阵列的一栏。
3)将我的数组保持为[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
}
答案 0 :(得分:2)
正如您在评论中指出的那样,这条线不起作用:
let csvColumns: [MyStruct] = i.components(separatedBy: ",")
components(separatedBy:)
方法始终返回一个字符串数组([String]
),因此您无法将该值设置为一个类型为MyStruct数组的变量({{1} })。
看起来你的MyStruct类型应该代表一整行数据(4个字符串和一个整数),所以我不认为列是MyStructs数组是正确的。第一个原因是这些不代表列,而是行,而且似乎是1 MyStruct == 1行值,而数据数组只是那些MyStruct行值的数组([MyStruct]
而不是你现在拥有的MyStruct实例数组([MyStruct]
)的数组。
那就是说,为了最接近你所拥有的,我建议用一个可以容纳String或Int的FieldValue枚举替换MyStruct。使用这种方法,您的代码将如下所示:
[[MyStruct]]
答案 1 :(得分:1)
您的<div *ngIf="timer">
<ion-item class="no-bottom-border item">
<button ion-button *ngIf="timeInSeconds && timeInSeconds > 0" large full clear class="timer-button timer-text">{{timer.displayTime}}</button>
</ion-item>
<ion-item class="no-bottom-border" *ngIf="timeInSeconds && timeInSeconds > 0">
<button ion-button icon-left clear color="danger" small (click)="initTimer()" item-left *ngIf="!timer.runTimer && (timer.hasStarted || timer.hasFinished) || timer.hasFinished">
<ion-icon name="refresh"></ion-icon>
Reset
</button>
<button ion-button icon-left clear small color="primary" (click)="pauseTimer()" item-right *ngIf="timer.runTimer && timer.hasStarted && !timer.hasFinished">
<ion-icon name="pause"></ion-icon>
Pause
</button>
<button ion-button icon-left clear small color="primary" (click)="resumeTimer()" item-right *ngIf="!timer.runTimer && timer.hasStarted && !timer.hasFinished">
<ion-icon name="play"></ion-icon>
Resume
</button>
<button ion-button icon-left clear small color="primary" (click)="startTimer()" item-right *ngIf="!timer.hasStarted">
<ion-icon name="play"></ion-icon>
Start
</button>
</ion-item>
</div>
已经是一行的结构。 MyStruct
的每个元素都是一列。因此,最终您的MyStruct
(dataArray
的返回值)应为func csvStringToArray(stringCSV: String)
,而不是[MyStruct]
。然后你的功能变为:
[[MyStruct]]
这显然假设您的数据类型和结构始终有效(您的行总是有4个字符串和一个整数,因此它们可以被索引或强制解包),否则您将不得不为此添加检查。 / p>
答案 2 :(得分:1)
如果您希望完全键入转换并避免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数据。Row
表示导入的csv数据中的单行。DataPoint
可以包含String
或Int
。请注意,这与Optional<T>
的工作方式非常相似。MyDataType
都有多个Row
s。Row
都有多个DataPoint
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:))
}
}
您现在可以简单地使用init(enforcingCustomFormatOn:)
,第1-4列将被视为字符串值。如果可能,只有第5列将转换为.integer。
请注意,您需要将rows = csvRows.flatMap(Row.init)
中的MyDataType.init(csv:)
更改为rows = csvRows.flatMap(Row.init(csv:))
。否则,.flatMap
将无法知道要选择哪个init,因为两者都具有相同的签名。