【问题标题】:How to use Swift's higher order functions for parsing dynamic dictionary data in swift?如何使用 Swift 的高阶函数快速解析动态字典数据?
【发布时间】:2020-03-08 19:13:14
【问题描述】:

我正在尝试解析以下 json 并希望检索其值与给定值匹配的字典的“键”。

{ "OuterArrayHolder" : 
  [
    { 
      "dictDynamicKey" : ["dynamicValue1", "dynamicValue2", "dynamicValue3"]
    },
    { 
      "dictAnotherDynamicKey" : ["dynamicValue4", "dynamicValue5", "dynamicValue6"]
    },
  ]
}

[注意:在上面的json中,所有的键和值都是动态的,除了“OuterArrayHolder”。]

我已经以非 Swifty 的方式实现了它,目前 获得了预期的输出,但我不知道如何使用 swift 的高阶函数来完成相同的行为.

输入:“dynamicValue2”

预期输出:“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 循环,并希望使用高阶函数来实现确切的行为,非常感谢这方面的任何帮助。

【问题讨论】:

  • for...in 的高阶函数版本是forEach。除此之外,很难看出您还有什么期望。我不确定我是否明白您对此的看法是“非 Swifty”。您正在做一些不必要的事情;例如,if !outerArrayHolder.isEmpty 毫无意义,因为如果它为空,则for...in 循环无论如何都不会执行。说if condition...continue...else...otherthing 是愚蠢的,因为你还不如说if !condition otherthing。但这与“高阶”无关;只是你过于冗长了。
  • @matt 除了不必要的东西,您能否建议是否有任何其他方法可以使用高阶函数检索 dynamicKey 并避免 for-in/forEach。
  • forEach 一个高阶函数。不清楚您想象的“高阶函数”会为您做什么或将为您做什么,这与您正在做的事情不同。循环就是循环,不管如何表达。
  • 你这样做了多少次?因为对字典数组进行线性搜索,对每个条目进行线性搜索,对每个值数组进行线性搜索对于大型数据集或频繁访问来说真的很慢。
  • @matt 我的意思是使用“过滤器”功能。像 outerArrayHolder.filter {($0...一些代码在这里.......)}

标签: ios swift for-loop swift4 higher-order-functions


【解决方案1】:

我们可以将您的算法转换为函数式风格吗?是的。这是个好主意吗?在这种情况下可能不是。但方法如下。

您没有提供任何类型信息,所以我将使用此类型:

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 {
        $0.lazy
            .filter { ($0.value as? [String])?.contains(inputValue) ?? false }
            .map { $0.key }
            .first }
    .first

但我们可以使用first(where:) 保存lazyfirst

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

【讨论】:

  • 谢谢 rob,我会试一试的。能否请您详细说明“outerArrayHolder.lazy”在这里的目的是什么,我们不能只使用“outerArrayHolder.compactMap”,请建议。
  • 如果你使用outerArrayHolder.compactMap,那么你将构造一个包含所有匹配键的数组,然后只保留第一个。如果您使用lazy,它会按需查找密钥。由于first 只要求第一个匹配项,因此您无需花费时间和内存查找所有匹配键。
  • 谢谢你,这就是我要找的。我将对此进行更多探索并在有任何查询时回复,以便如果其他人有相同的查询,您的解决方案会有所帮助。
【解决方案2】:

我看不出这与高阶函数有什么关系。如果外键是已知的,我会简单地写

// 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
}

这正是你正在做的,只是它是干净的。

【讨论】:

  • 是的,我同意您的解决方案和观点,但我要做的是删除外部 for 循环(也可能是外部和内部循环)并将其替换为“过滤器”,这也将帮助我了解如何在此类数据中使用“过滤器”。
  • @iLearner filter 不是适合这项工作的工具。 filter 的结果是 [T](多个 Ts),但您只是在寻找一个值。此外,filter 总是遍历目标序列的所有元素,这也不是您想要的。此外,使用filter“不会摆脱循环”,它只是隐藏它。在内部 filter 正在使用 for-loop through。
  • @iLearner 你不会说“我只会接受一个以 x 方式工作的答案”。 “方式 x”是否真的有效,或者它是否是“最佳”方式,这正是你所不知道的。这就是为什么你有一个问题。如果你知道你是否可以适当地施加这种条件,你就已经知道问题的答案了。
  • 这不是要接受答案,你的 cmets 真的很有帮助,但我要问的是,有没有更好的方法来处理当前通过两个循环迭代所做的相同事情。
  • @iLearner 很高兴您提出这个问题,但现在您需要倾听答案。首先,“更好”是一个见仁见智的问题。更好的如何?以什么标准或衡量标准?快点?更漂亮?更快捷?我向你展示了一个更漂亮、更快捷的版本。其次,像mapfilter这样的“高阶”函数循环;无论是 you 循环还是函数为你循环都没有区别,它是同一个循环。使用高阶函数提前退出所需的舞蹈可以说是丑陋且难以理解。所以不清楚你在问什么。
【解决方案3】:

没有高阶函数可以精确地满足您的需求。最接近的是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")

但它可能比它可能的价值更复杂。我会推荐马特的解决方案。

规模化解决方案

您尚未对此进行澄清,但我怀疑您可能需要多次执行此操作。在这种情况下,线性搜索会变得非常慢。通过按值搜索键,您并没有利用字典的主要优势:通过键对值进行恒定时间访问。你的代码是:

  1. 线性搜索字典数组,引入O(dictionaries.count) 因子
  2. 对于 #1 中数组中的每个 dict,对键/值对进行线性搜索,这会引入 O(dict.count) 因子
  3. 对于 #2 中 dict 中的每个键/值对,对值数组进行线性搜索,这会引入 O(valueArray.count) 因子。

总时间复杂度乘以O(dictionaries.count * averageDict.count * averageValueArray.count),变得非常慢非常快。

相反,您可以预先花费一些计算成本来创建一个新的数据结构,该结构能够更好地为您想要在其上运行的各种查询提供服务。在这种情况下,您可以“反转”字典。

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 { $0.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")

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多