如何使用Swift的高阶函数解析swift中的动态字典数据?
How to use Swift's higher order functions for parsing dynamic dictionary data in swift?
我正在尝试解析以下 json 并希望检索其值与给定值匹配的字典的“键”。
{ "OuterArrayHolder" :
[
{
"dictDynamicKey" : ["dynamicValue1", "dynamicValue2", "dynamicValue3"]
},
{
"dictAnotherDynamicKey" : ["dynamicValue4", "dynamicValue5", "dynamicValue6"]
},
]
}
[注意:在上面json中,除了“OuterArrayHolder”之外的所有键和值都是动态的。]
我已经以非 Swifty 方式实现了它,目前 获得了预期的输出 ,但我不知道如何使用 swift的高阶函数.
Input : "dynamicValue2"
Expected output : "dictDynamicKey"
当前解决方案:
let inputValue = "dynamicValue2"
if !outerArrayHolder.isEmpty {
for dynamicDict in outerArrayHolder {
for (key, value) in dynamicDict {
if value.empty || !value.contains(inputValue) {
continue
} else {
//here if inputValue matches in contianed array (value is array in dictionary) then I want to use its "repective key" for further businisess logic.
}
}
}
}
我想减少这两个 for 循环,并想使用高阶函数来实现确切的行为,非常感谢这方面的任何帮助。
我看不出这与高阶函数有什么关系。如果知道外键,我会简单地写
// just building your structure
let d1 = ["dictDynamicKey" : ["dynamicValue1", "dynamicValue2", "dynamicValue3"]]
let d2 = ["dictAnotherDynamicKey" : ["dynamicValue4", "dynamicValue5", "dynamicValue6"]]
let d = ["OuterArrayHolder" : [d1, d2]]
// this is the actual code:
func find(_ target:String) -> String? {
for dict in d["OuterArrayHolder"]! {
for (k,v) in dict {
if v.contains(target) {return k}
}
}
return nil
}
这就是你在做的,只是它很干净。
没有高阶函数可以精确地满足您的需求。最接近的是 first(where:)
,但问题是结果只是 Bool
,你没有办法干净地捞出与找到的案例相关的数据。
你可以这样写:
extension Sequence {
func findFirst<T>(where predicate: (Element) throws -> T?) rethrows -> T? {
for element in self {
if let result = try predicate(element) {
return result
}
}
return nil
}
}
然后像这样使用它:
let dictionaries = [
[
"dictDynamicKey" : ["dynamicValue1", "dynamicValue2", "dynamicValue3"]
],
[
"dictAnotherDynamicKey" : ["dynamicValue4", "dynamicValue5", "dynamicValue6"]
],
]
let desiredValue = "dynamicValue2"
extension Sequence {
func findFirst<T>(where predicate: (Element) throws -> T?) rethrows -> T? {
for element in self {
if let result = try predicate(element) {
return result
}
}
return nil
}
}
let result = dictionaries.findFirst(where: { dict in
dict.findFirst(where: { key, values in
values.contains(desiredValue) ? key : nil
})
})
print(result as Any) // => Optional("dictDynamicKey")
但它可能比它的价值更复杂。我会推荐 Matt 的解决方案。
缩放解决方案
你还没有澄清这一点,但我怀疑你可能需要多次这样做。在那种情况下,线性搜索会变得非常慢。通过按值搜索键,您没有利用字典的关键优势:通过键对值进行恒定时间访问。您的代码是:
- 通过字典数组进行线性搜索,引入一个
O(dictionaries.count)
因子
- 对于#1 中数组中的每个字典,通过 key/value 对进行线性搜索,这引入了一个
O(dict.count)
因子
- 对于 #2 中字典中的每个 key/value 对,线性搜索值数组,这引入了一个
O(valueArray.count)
因子。
总时间复杂度乘以 O(dictionaries.count * averageDict.count * averageValueArray.count)
,变得非常慢非常快。
相反,您可以预先花费一些计算成本来创建一个新的数据结构,该数据结构能够更好地服务您想要 运行 的查询类型。在这种情况下,您可以 "invert" 字典。
extension Dictionary {
func inverted<T>() -> [T: Key] where Dictionary.Value == [T] {
let invertedKeyValuePairs = self
.lazy
.flatMap { oldKey, oldValues in
oldValues.map { oldValue in (key: oldValue, value: oldKey) as (T, Key) }
}
return Dictionary<T, Key>(uniqueKeysWithValues: invertedKeyValuePairs)
}
}
// Example usage:
let valuesByKeys = [
"a": [1, 2, 3],
"b": [4, 5, 6]
]
let keysPerValue = valuesByKeys.inverted()
keysPerValue.forEach { key, value in print("key: \(key), value: \(value)") }
// Which results in:
// key: 3, value: a
// key: 4, value: b
// key: 5, value: b
// key: 1, value: a
// key: 6, value: b
// key: 2, value: a
鉴于这样的 inverted
实现,您可以反转输入集中的每个字典,并将它们全部合并在一起:
let invertedDictionary = Dictionary(uniqueKeysWithValues: dictionaries.flatMap { [=13=].inverted() })
invertedDictionary.forEach { key, value in print("key: \(key), value: \(value)") }
// Result:
key: dynamicValue6, value: dictAnotherDynamicKey
key: dynamicValue1, value: dictDynamicKey
key: dynamicValue2, value: dictDynamicKey
key: dynamicValue3, value: dictDynamicKey
key: dynamicValue4, value: dictAnotherDynamicKey
key: dynamicValue5, value: dictAnotherDynamicKey
您可以存储和共享此字典,它可以提供恒定时间 (O(1)
) 访问与任何所需值关联的键:
print(invertedDictionary[desiredValue] as Any) // => Optional("dictDynamicKey")
我们可以将您的算法转换为函数式风格吗?是的。这是个好主意吗?在这种情况下可能不是。但方法是这样的。
您没有提供任何类型信息,所以我将使用此类型:
let outerArrayHolder: [[String: Any]] = [
[
"dictDynamicKey": ["dynamicValue1", "dynamicValue2", "dynamicValue3"]
],
[
"dictAnotherDynamicKey" : ["dynamicValue4", "dynamicValue5", "dynamicValue6"]
],
]
而你想找到包含inputValue
:
的数组对应的key
let inputValue = "dynamicValue2"
功能策略是将outerArrayHolder
中的每个字典映射到它的第一个具有匹配值的键。如果字典没有这样的键,则字典映射为 nil。然后我们扔掉nils,取第一个剩余的值。
我们可以根据要求用 filter
来完成:
let key = outerArrayHolder.lazy
.compactMap {
[=12=].lazy
.filter { ([=12=].value as? [String])?.contains(inputValue) ?? false }
.map { [=12=].key }
.first }
.first
但我们可以使用 first(where:)
:
保存 lazy
和 first
let key = outerArrayHolder.lazy
.compactMap({
[=13=]
.first(where: { ([=13=].value as? [String])?.contains(inputValue) ?? false })
.map { [=13=].key }
}).first
我正在尝试解析以下 json 并希望检索其值与给定值匹配的字典的“键”。
{ "OuterArrayHolder" :
[
{
"dictDynamicKey" : ["dynamicValue1", "dynamicValue2", "dynamicValue3"]
},
{
"dictAnotherDynamicKey" : ["dynamicValue4", "dynamicValue5", "dynamicValue6"]
},
]
}
[注意:在上面json中,除了“OuterArrayHolder”之外的所有键和值都是动态的。]
我已经以非 Swifty 方式实现了它,目前 获得了预期的输出 ,但我不知道如何使用 swift的高阶函数.
Input : "dynamicValue2"
Expected output : "dictDynamicKey"
当前解决方案:
let inputValue = "dynamicValue2"
if !outerArrayHolder.isEmpty {
for dynamicDict in outerArrayHolder {
for (key, value) in dynamicDict {
if value.empty || !value.contains(inputValue) {
continue
} else {
//here if inputValue matches in contianed array (value is array in dictionary) then I want to use its "repective key" for further businisess logic.
}
}
}
}
我想减少这两个 for 循环,并想使用高阶函数来实现确切的行为,非常感谢这方面的任何帮助。
我看不出这与高阶函数有什么关系。如果知道外键,我会简单地写
// just building your structure
let d1 = ["dictDynamicKey" : ["dynamicValue1", "dynamicValue2", "dynamicValue3"]]
let d2 = ["dictAnotherDynamicKey" : ["dynamicValue4", "dynamicValue5", "dynamicValue6"]]
let d = ["OuterArrayHolder" : [d1, d2]]
// this is the actual code:
func find(_ target:String) -> String? {
for dict in d["OuterArrayHolder"]! {
for (k,v) in dict {
if v.contains(target) {return k}
}
}
return nil
}
这就是你在做的,只是它很干净。
没有高阶函数可以精确地满足您的需求。最接近的是 first(where:)
,但问题是结果只是 Bool
,你没有办法干净地捞出与找到的案例相关的数据。
你可以这样写:
extension Sequence {
func findFirst<T>(where predicate: (Element) throws -> T?) rethrows -> T? {
for element in self {
if let result = try predicate(element) {
return result
}
}
return nil
}
}
然后像这样使用它:
let dictionaries = [
[
"dictDynamicKey" : ["dynamicValue1", "dynamicValue2", "dynamicValue3"]
],
[
"dictAnotherDynamicKey" : ["dynamicValue4", "dynamicValue5", "dynamicValue6"]
],
]
let desiredValue = "dynamicValue2"
extension Sequence {
func findFirst<T>(where predicate: (Element) throws -> T?) rethrows -> T? {
for element in self {
if let result = try predicate(element) {
return result
}
}
return nil
}
}
let result = dictionaries.findFirst(where: { dict in
dict.findFirst(where: { key, values in
values.contains(desiredValue) ? key : nil
})
})
print(result as Any) // => Optional("dictDynamicKey")
但它可能比它的价值更复杂。我会推荐 Matt 的解决方案。
缩放解决方案
你还没有澄清这一点,但我怀疑你可能需要多次这样做。在那种情况下,线性搜索会变得非常慢。通过按值搜索键,您没有利用字典的关键优势:通过键对值进行恒定时间访问。您的代码是:
- 通过字典数组进行线性搜索,引入一个
O(dictionaries.count)
因子 - 对于#1 中数组中的每个字典,通过 key/value 对进行线性搜索,这引入了一个
O(dict.count)
因子 - 对于 #2 中字典中的每个 key/value 对,线性搜索值数组,这引入了一个
O(valueArray.count)
因子。
总时间复杂度乘以 O(dictionaries.count * averageDict.count * averageValueArray.count)
,变得非常慢非常快。
相反,您可以预先花费一些计算成本来创建一个新的数据结构,该数据结构能够更好地服务您想要 运行 的查询类型。在这种情况下,您可以 "invert" 字典。
extension Dictionary {
func inverted<T>() -> [T: Key] where Dictionary.Value == [T] {
let invertedKeyValuePairs = self
.lazy
.flatMap { oldKey, oldValues in
oldValues.map { oldValue in (key: oldValue, value: oldKey) as (T, Key) }
}
return Dictionary<T, Key>(uniqueKeysWithValues: invertedKeyValuePairs)
}
}
// Example usage:
let valuesByKeys = [
"a": [1, 2, 3],
"b": [4, 5, 6]
]
let keysPerValue = valuesByKeys.inverted()
keysPerValue.forEach { key, value in print("key: \(key), value: \(value)") }
// Which results in:
// key: 3, value: a
// key: 4, value: b
// key: 5, value: b
// key: 1, value: a
// key: 6, value: b
// key: 2, value: a
鉴于这样的 inverted
实现,您可以反转输入集中的每个字典,并将它们全部合并在一起:
let invertedDictionary = Dictionary(uniqueKeysWithValues: dictionaries.flatMap { [=13=].inverted() })
invertedDictionary.forEach { key, value in print("key: \(key), value: \(value)") }
// Result:
key: dynamicValue6, value: dictAnotherDynamicKey
key: dynamicValue1, value: dictDynamicKey
key: dynamicValue2, value: dictDynamicKey
key: dynamicValue3, value: dictDynamicKey
key: dynamicValue4, value: dictAnotherDynamicKey
key: dynamicValue5, value: dictAnotherDynamicKey
您可以存储和共享此字典,它可以提供恒定时间 (O(1)
) 访问与任何所需值关联的键:
print(invertedDictionary[desiredValue] as Any) // => Optional("dictDynamicKey")
我们可以将您的算法转换为函数式风格吗?是的。这是个好主意吗?在这种情况下可能不是。但方法是这样的。
您没有提供任何类型信息,所以我将使用此类型:
let outerArrayHolder: [[String: Any]] = [
[
"dictDynamicKey": ["dynamicValue1", "dynamicValue2", "dynamicValue3"]
],
[
"dictAnotherDynamicKey" : ["dynamicValue4", "dynamicValue5", "dynamicValue6"]
],
]
而你想找到包含inputValue
:
let inputValue = "dynamicValue2"
功能策略是将outerArrayHolder
中的每个字典映射到它的第一个具有匹配值的键。如果字典没有这样的键,则字典映射为 nil。然后我们扔掉nils,取第一个剩余的值。
我们可以根据要求用 filter
来完成:
let key = outerArrayHolder.lazy
.compactMap {
[=12=].lazy
.filter { ([=12=].value as? [String])?.contains(inputValue) ?? false }
.map { [=12=].key }
.first }
.first
但我们可以使用 first(where:)
:
lazy
和 first
let key = outerArrayHolder.lazy
.compactMap({
[=13=]
.first(where: { ([=13=].value as? [String])?.contains(inputValue) ?? false })
.map { [=13=].key }
}).first