Как использовать функции более высокого порядка Swift для анализа данных словаря Dynami c в Swift? - PullRequest
0 голосов
/ 08 марта 2020

Я пытаюсь проанализировать следующее json и хочу получить «ключ» словаря, значение которого совпадает с заданным значением.

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

[Примечание: здесь выше json все ключи и значения являются динамическими c, кроме "OuterArrayHolder".]

Я реализовал его не-Swifty и в настоящее время получаю ожидаемый результат , но я не получить, как выполнить sh такое же поведение, используя функции высшего порядка 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.
        }
      }
   }
}

Я хочу уменьшить эти два для циклов и хочу использовать функции более высокого порядка для достижения точного поведения, Любая помощь в этом отношении очень ценится .

Ответы [ 3 ]

1 голос
/ 08 марта 2020

Можем ли мы преобразовать ваш алгоритм в функциональный стиль? Да. Это хорошая идея? Вероятно, не в этом случае. Но вот как.

Вы не предоставили никакой информации о типе, поэтому я буду использовать этот тип:

let outerArrayHolder: [[String: Any]] = [
    [
        "dictDynamicKey": ["dynamicValue1", "dynamicValue2", "dynamicValue3"]
    ],
    [
        "dictAnotherDynamicKey" : ["dynamicValue4", "dynamicValue5", "dynamicValue6"]
    ],
]

И вы хотите найти ключ, соответствующий массиву, который содержит inputValue:

let inputValue = "dynamicValue2"

Функциональная стратегия состоит в том, чтобы сопоставить каждый словарь в outerArrayHolder его первому ключу, который имеет соответствующее значение. Если словарь не имеет такого ключа, словарь сопоставляется с нулем. Затем мы отбрасываем нули и принимаем первое оставшееся значение.

Мы можем сделать это с помощью filter, как было запрошено:

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

Но мы можем сохранить lazy и first с использованием first(where:):

let key = outerArrayHolder.lazy
    .compactMap({
        $0
            .first(where: { ($0.value as? [String])?.contains(inputValue) ?? false })
            .map { $0.key }
    }).first
0 голосов
/ 08 марта 2020

Нет функции высшего порядка, которая делает именно то, что вы ищете. Ближайшим является first(where:), но проблема в том, что в результате получается просто Bool, и у вас нет способа точно определить sh данные, связанные с найденным случаем.

Вы можете напишите что-то вроде:

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. Для каждого dict в массиве в # 1, линейный поиск по ключу пары / значение, которое вводит O(dict.count) коэффициент
  3. Для каждой пары ключ / значение в dict в # 2, линейный поиск по массиву значений, который вводит 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 вы можете инвертировать каждый dict вашего входного набора и объединить их все вместе:

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")
0 голосов
/ 08 марта 2020

Я не понимаю, как это связано с функциями более высокого порядка. Если внешний ключ известен, я бы просто написал:

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

Это именно то, что вы делаете, только это чисто.

...