当每个键有 5 个值时,将字典值从字符串转换为双精度值
Convert dictionary values from string to double when there are 5 values per key
我正在尝试学习如何使用 Swift 中的词典。我的程序读取一个本地 JSON 文件,其中每个键包含五个值元素,并将其放入字典中。键是字符串格式“yyyy-MM-dd”的日期,值表示字符串格式的 409.34593 美元。我已经能够对字典进行排序和过滤了。
此外,使用 mapValues() 创建一个新字典,其中包含一个键和一个值,并将值转换为可选的双精度值。令我困惑的是如何将所有 5 个值元素转换为双精度,即具有与从 JSON 文件中读取的相同的字典,但所有 5 个值都是双精度或可选双精度。
下面是 JSON 文件和我的代码的片段。如果您能为我指明正确的方向,我们将不胜感激。
"Time Series (Daily)": {
"2021-09-22": {
"1. open": "402.1700",
"2. high": "405.8500",
"3. low": "401.2600",
"4. close": "403.9000",
"5. volume": "5979811"
},
"2021-09-21": {
"1. open": "402.6600",
"2. high": "403.9000",
"3. low": "399.4400",
"4. close": "400.0400",
"5. volume": "6418124"
},
struct Stock: Codable {
let timeSeriesDaily: [String : TimeSeriesDaily] // creates a dictionary, key = string : TimeSeriesDaily = value
enum CodingKeys: String, CodingKey {
case timeSeriesDaily = "Time Series (Daily)"
}
}
struct TimeSeriesDaily: Codable {
let Open, High, Low, Close: String
let Volume: String
enum CodingKeys: String, CodingKey {
case Open = "1. open"
case High = "2. high"
case Low = "3. low"
case Close = "4. close"
case Volume = "5. volume"
}
}
class ReadData: ObservableObject {
@Published var tmpData = Stock(timeSeriesDaily : [:]) // initialize dictionary as empty when struct Stock is created
// type info has to be made availabe upon creation, is done in the Stock struct
init() {
loadData()
}
func loadData() {
guard let url = Bundle.main.url(forResource: "VOO", withExtension: "json")
else {
print("Json file not found")
return
}
let decoder = JSONDecoder()
do {
let data = try Data(contentsOf: url)
self.tmpData = try decoder.decode(Stock.self, from: data)
// print(self.tmpData)
} catch {
print(error)
}
}
}
struct ContentView: View {
@ObservedObject var vooData = ReadData()
var body: some View {
ScrollView {
VStack (alignment: .leading) {
let lastYear = getOneYearAgo()
let filteredDict = vooData.tmpData.timeSeriesDaily.filter { [=10=].key > lastYear } // Works
let sortedFilteredDict = filteredDict.sorted { [=10=].key < .key } // Works
let justCloseArray = sortedFilteredDict.map { ([=10=].key, [=10=].value.Close) } // returns array [(String, String)]
let justCloseDict = Dictionary(uniqueKeysWithValues: justCloseArray) // returns dictionary with 1 key & 1 val
let sortedCloseDict = justCloseDict.sorted { [=10=].key < .key } // works
let newDict = filteredDict.mapValues { Double([=10=].Close) }
let sortedNewDict = newDict.sorted { [=10=].key < .key }
Spacer()
// ForEach ( sortedCloseDict.map { ([=10=].key, [=10=].value) }, id: \.0 ) { keyValuePair in
ForEach ( sortedNewDict.map { ([=10=].key, [=10=].value) }, id: \.0 ) { keyValuePair in // map converts dictionary to arrap
HStack {
Text (keyValuePair.0)
Text ("\(keyValuePair.1!)")
}
}
Spacer()
} // end vstack
} .frame(width: 600, height: 400, alignment: .center) // end scroll view
}
}
这里的问题是你的美元值不是数字,它们是字符串。
把它放在 Playground 中:
import Foundation
let jsonData = """
{
"Time Series (Daily)": {
"2021-09-22": {
"1. open": "402.1700",
"2. high": "405.8500",
"3. low": "401.2600",
"4. close": "403.9000",
"5. volume": "5979811"
},
"2021-09-21": {
"1. open": "402.6600",
"2. high": "403.9000",
"3. low": "399.4400",
"4. close": "400.0400",
"5. volume": "6418124"
}
}
}
"""
.data(using: .utf8)!
struct ReadData {
let timeSeriesDaily: [Date: TimeSeriesDaily]
}
struct TimeSeriesDaily {
let open: Double
let high: Double
let low: Double
let close: Double
let volume: Double
}
extension ReadData: Decodable {
enum CodingKeys: String, CodingKey {
case timeSeriesDaily = "Time Series (Daily)"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
timeSeriesDaily = Dictionary(
uniqueKeysWithValues: try container
.decode([String: TimeSeriesDaily].self, forKey: .timeSeriesDaily)
.map { (dateFormatter.date(from: [=10=])!, ) }
)
}
}
/** In the above, the container can decode [String: T] but it can't decode [Date: T], so we convert the Strings to Dates using a dateFormatter */
extension TimeSeriesDaily: Decodable {
enum CodingKeys: String, CodingKey {
case open = "1. open"
case high = "2. high"
case low = "3. low"
case close = "4. close"
case volume = "5. volume"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
open = Double(try container.decode(String.self, forKey: .open))!
high = Double(try container.decode(String.self, forKey: .high))!
low = Double(try container.decode(String.self, forKey: .low))!
close = Double(try container.decode(String.self, forKey: .close))!
volume = Double(try container.decode(String.self, forKey: .volume))!
}
}
/** Since the values in the json are actually Strings, we decode them as such and then convert to Doubles. */
let dateFormatter: DateFormatter = {
let result = DateFormatter()
result.dateFormat = "yyyy-MM-dd"
return result
}()
print(try JSONDecoder().decode(ReadData.self, from: jsonData))
如果你不喜欢所有的force casting,你可以引入一个castOrThrow
类型的函数。
我正在尝试学习如何使用 Swift 中的词典。我的程序读取一个本地 JSON 文件,其中每个键包含五个值元素,并将其放入字典中。键是字符串格式“yyyy-MM-dd”的日期,值表示字符串格式的 409.34593 美元。我已经能够对字典进行排序和过滤了。
此外,使用 mapValues() 创建一个新字典,其中包含一个键和一个值,并将值转换为可选的双精度值。令我困惑的是如何将所有 5 个值元素转换为双精度,即具有与从 JSON 文件中读取的相同的字典,但所有 5 个值都是双精度或可选双精度。
下面是 JSON 文件和我的代码的片段。如果您能为我指明正确的方向,我们将不胜感激。
"Time Series (Daily)": {
"2021-09-22": {
"1. open": "402.1700",
"2. high": "405.8500",
"3. low": "401.2600",
"4. close": "403.9000",
"5. volume": "5979811"
},
"2021-09-21": {
"1. open": "402.6600",
"2. high": "403.9000",
"3. low": "399.4400",
"4. close": "400.0400",
"5. volume": "6418124"
},
struct Stock: Codable {
let timeSeriesDaily: [String : TimeSeriesDaily] // creates a dictionary, key = string : TimeSeriesDaily = value
enum CodingKeys: String, CodingKey {
case timeSeriesDaily = "Time Series (Daily)"
}
}
struct TimeSeriesDaily: Codable {
let Open, High, Low, Close: String
let Volume: String
enum CodingKeys: String, CodingKey {
case Open = "1. open"
case High = "2. high"
case Low = "3. low"
case Close = "4. close"
case Volume = "5. volume"
}
}
class ReadData: ObservableObject {
@Published var tmpData = Stock(timeSeriesDaily : [:]) // initialize dictionary as empty when struct Stock is created
// type info has to be made availabe upon creation, is done in the Stock struct
init() {
loadData()
}
func loadData() {
guard let url = Bundle.main.url(forResource: "VOO", withExtension: "json")
else {
print("Json file not found")
return
}
let decoder = JSONDecoder()
do {
let data = try Data(contentsOf: url)
self.tmpData = try decoder.decode(Stock.self, from: data)
// print(self.tmpData)
} catch {
print(error)
}
}
}
struct ContentView: View {
@ObservedObject var vooData = ReadData()
var body: some View {
ScrollView {
VStack (alignment: .leading) {
let lastYear = getOneYearAgo()
let filteredDict = vooData.tmpData.timeSeriesDaily.filter { [=10=].key > lastYear } // Works
let sortedFilteredDict = filteredDict.sorted { [=10=].key < .key } // Works
let justCloseArray = sortedFilteredDict.map { ([=10=].key, [=10=].value.Close) } // returns array [(String, String)]
let justCloseDict = Dictionary(uniqueKeysWithValues: justCloseArray) // returns dictionary with 1 key & 1 val
let sortedCloseDict = justCloseDict.sorted { [=10=].key < .key } // works
let newDict = filteredDict.mapValues { Double([=10=].Close) }
let sortedNewDict = newDict.sorted { [=10=].key < .key }
Spacer()
// ForEach ( sortedCloseDict.map { ([=10=].key, [=10=].value) }, id: \.0 ) { keyValuePair in
ForEach ( sortedNewDict.map { ([=10=].key, [=10=].value) }, id: \.0 ) { keyValuePair in // map converts dictionary to arrap
HStack {
Text (keyValuePair.0)
Text ("\(keyValuePair.1!)")
}
}
Spacer()
} // end vstack
} .frame(width: 600, height: 400, alignment: .center) // end scroll view
}
}
这里的问题是你的美元值不是数字,它们是字符串。
把它放在 Playground 中:
import Foundation
let jsonData = """
{
"Time Series (Daily)": {
"2021-09-22": {
"1. open": "402.1700",
"2. high": "405.8500",
"3. low": "401.2600",
"4. close": "403.9000",
"5. volume": "5979811"
},
"2021-09-21": {
"1. open": "402.6600",
"2. high": "403.9000",
"3. low": "399.4400",
"4. close": "400.0400",
"5. volume": "6418124"
}
}
}
"""
.data(using: .utf8)!
struct ReadData {
let timeSeriesDaily: [Date: TimeSeriesDaily]
}
struct TimeSeriesDaily {
let open: Double
let high: Double
let low: Double
let close: Double
let volume: Double
}
extension ReadData: Decodable {
enum CodingKeys: String, CodingKey {
case timeSeriesDaily = "Time Series (Daily)"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
timeSeriesDaily = Dictionary(
uniqueKeysWithValues: try container
.decode([String: TimeSeriesDaily].self, forKey: .timeSeriesDaily)
.map { (dateFormatter.date(from: [=10=])!, ) }
)
}
}
/** In the above, the container can decode [String: T] but it can't decode [Date: T], so we convert the Strings to Dates using a dateFormatter */
extension TimeSeriesDaily: Decodable {
enum CodingKeys: String, CodingKey {
case open = "1. open"
case high = "2. high"
case low = "3. low"
case close = "4. close"
case volume = "5. volume"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
open = Double(try container.decode(String.self, forKey: .open))!
high = Double(try container.decode(String.self, forKey: .high))!
low = Double(try container.decode(String.self, forKey: .low))!
close = Double(try container.decode(String.self, forKey: .close))!
volume = Double(try container.decode(String.self, forKey: .volume))!
}
}
/** Since the values in the json are actually Strings, we decode them as such and then convert to Doubles. */
let dateFormatter: DateFormatter = {
let result = DateFormatter()
result.dateFormat = "yyyy-MM-dd"
return result
}()
print(try JSONDecoder().decode(ReadData.self, from: jsonData))
如果你不喜欢所有的force casting,你可以引入一个castOrThrow
类型的函数。