如何使用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 的解决方案。

缩放解决方案

你还没有澄清这一点,但我怀疑你可能需要多次这样做。在那种情况下,线性搜索会变得非常慢。通过按值搜索键,您没有利用字典的关键优势:通过键对值进行恒定时间访问。您的代码是:

  1. 通过字典数组进行线性搜索,引入一个 O(dictionaries.count) 因子
  2. 对于#1 中数组中的每个字典,通过 key/value 对进行线性搜索,这引入了一个 O(dict.count) 因子
  3. 对于 #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:):

保存 lazyfirst
let key = outerArrayHolder.lazy
    .compactMap({
        [=13=]
            .first(where: { ([=13=].value as? [String])?.contains(inputValue) ?? false })
            .map { [=13=].key }
    }).first