Swift replace key value in array of dictionaries with nested dictionaries - swift

I have swift dictionary [String: Any] which I store in UserDefauls as an array [[String: Any]]
what I want to do is replace key: value with another one, e.g. "id": "x:coredataid"with"id": "server id"
I need to loop through array first and then through all key values. Is there any elegant solution for this purposes?
If not how then simple iterate through all key values and all nested levels in dictionary?
I have this code: for (key, value) in params
but it's only for top level keys.
Let me explain more in details. As you see I have phases key which an array. Also each phase contains day key which also an array.
So I don't care actually about key naming, phases it or days whether, what I want is to iterate all of key, values from provided [String: Any] dictionary and check if key contains a value which equal provided string.
As you see currently workoutId equals: <x-coredata://C3C82F5A-8709-4EDC-8AE5-C23C65F220D5/WorkoutEntity/t072831FB-8F5C-4589-85CB-6D084671C097815> I underscore it with red line.
So I want to loop a dictionary to catch this key workoutId and check if this equal <x-coredata://C3C82F5A-8709-4EDC-8AE5-C23C65F220D5/WorkoutEntity/t072831FB-8F5C-4589-85CB-6D084671C097815>
One more time I don't care about workoutId name, key can be actually named as exerciseId or id never mind. I just want to find a value <x-coredata://C3C82F5A-8709-4EDC-8AE5-C23C65F220D5/WorkoutEntity/t072831FB-8F5C-4589-85CB-6D084671C097815> in my entire dictionary and if there are lot of them replace all of them.
The reason why I need it is connected to identifier I store localy which are equals to CoreData identifiers as you my noticed. But when I modified my CoreData records with new identifiers returned from server I want to replace my UserDefaults off-line requests store with new ids.
I've added also modification to this code:
func update(_ dict: [String: Any], set serverId: Any, for localId: String) -> [String: Any] {
var newDict = dict
for (k, v) in newDict {
if let mobileLocalId = v as? String {
if mobileLocalId == localId {
newDict[k] = serverId
} else { newDict[k] = v }
} else if let subDict = v as? [String: Any] {
newDict[k] = update(subDict, set: serverId, for: localId)
} else if let dictionaries = v as? [[String: Any]] {
for dictionary in dictionaries {
newDict[k] = update(dictionary, set: serverId, for: localId)
}
}
}
return newDict
}
but it somehow drop days for me and newDict now looks like this:
(lldb) po newDict
▿ 2 elements
▿ 0 : 2 elements
- key : "position"
- value : 0
▿ 1 : 2 elements
- key : "workoutId"
- value : "5d51723b3faceb53f9d2d5ed"
where actully I susscefully changed identifiers, but now all other key pairs from above example are missed.

Here is a solution with a recursive function that replaces all values for a given key.
func update(_ dict: [String: Any], set value: Any, for key: String) -> [String: Any] {
var newDict = dict
for (k, v) in newDict {
if k == key {
newDict[k] = value
} else if let subDict = v as? [String: Any] {
newDict[k] = update(subDict, set: value, for: key)
} else if let subArray = v as? [[String: Any]] {
var newArray = [[String: Any]]()
for item in subArray {
newArray.append(update(item, set: value, for: key))
}
newDict[k] = newArray
}
}
return newDict
}
Note that it doesn't check what type the existing value is but directly replaces it with the new value. Also the code assumes the only types of nested arrays are arrays of dictionaries.
For the array this function can be used with map
let out = data.map { update($0, set: "newValue", for: "id")}

This recursive function will iterate through all key values:
func iterateThroughAllKeyValues<Key: Hashable, Value>(of dictionary: Dictionary<Key, Value>, execute execution: ((Key, Value))->()) {
for element in dictionary {
if let dictionary = element.value as? [Key: Value] {
iterateThroughAllKeyValues(of: dictionary, execute: execution)
} else {
execution(element)
}
}
}
Also you can achieve calling execution on the main node of any nested dictionary with a little bit of change.
And this is the extension mode:
extension Dictionary {
func iterateThroughAllKeyValues(execute execution: ((Key, Value))->()) {
for element in self {
if let dictionary = element.value as? [Key: Value] {
dictionary.iterateThroughAllKeyValues(execute: execution)
} else {
execution(element)
}
}
}
}
Note: Careful about the order
Usage Example:
let dictionary: [String: Any] = [
"id0": "value0",
"nested": ["id1": "value1"],
"nestedNested": ["id2": "value2",
"nested": ["id3": "value3"]]
]
dictionary.iterateThroughAllKeyValues { (key, value) in
print("key:", key, "Value:", value)
}
Output:
key: id0 Value: value0
key: id1 Value: value1
key: id3 Value: value3
key: id2 Value: value2

Related

How to initialize a case insensitive dictionary using Swift?

My problem is that this code is case-sensitive. If I have "Sam" and "sam", they will be sorted into different keys. Any way that I can think of doing this is by converting the string into all lowercase, but I want it to stay as normal while being sorted without case-sensitivity:
var dict: [String: [String]] = [:]
for string in array {
if (dict[string] != nil) {
dict[string]?.append(string)
}
else {
dict[string] = [string]
}
}
As it is right now my code would result in:
["Sam": ["Sam"], "sam", ["sam"]]
Instead of what I want:
["Sam": ["Sam", "sam"]]
How can I accomplish this?
You can use reduce(into:) method and assign each element capitalized to the result:
let array = ["Sam", "sam", "SAM"]
let dict: [String: [String]] = array.reduce(into: [:]) {
$0[$1.capitalized, default: []].append($1)
}
print(dict) // ["Sam": ["Sam", "sam", "SAM"]]
If you just want to have case insensitive keys and case sensitive values, from given array, the shortest solution could be something like this:
var dict: [String: [String]] = [:]
array.forEach { dict[$0.lowercased(), default: []] += [$0] }

How do I group by a key that could be nil and get a dictionary with a non-optional key type?

Let's say I have a model like this:
enum UKCountry : String {
case england, scotland, wales, northernIreland
init?(placeName: String) {
// magic happens here
}
}
struct LocalPopulation {
let place: String
let population: Int
}
The init(placeName:) takes an arbitrary string and figures out where it is. For example init(placeName: "London") gives .england. How it does this is not important.
I also have a [LocalPopulation] I want to process. Specifically, I want to get a [UKCountry: [LocalPopulation]]. I could do this:
let populations: [LocalPopulation] = [...]
let dict = Dictionary(grouping: populations, by: { UKCountry(placeName: $0.place)! })
But not all places in the populations is a place in the UK, so UKCountry.init would return nil, and it would crash. Note that I do not want any place that is not in the UK in the result. I could filter it before hand:
let populations: [LocalPopulation] = [...]
let filteredPopulations = populations.filter { UKCountry(placeName: $0.place) != nil }
let dict = Dictionary(grouping: populations, by: { UKCountry(placeName: $0.place)! })
But that means running UKCountry.init twice. Depending on how it is implemented, this could be costly. The other way I thought of is:
let populations: [LocalPopulation] = [...]
var dict = Dictionary(grouping: populations, by: { UKCountry(placeName: $0.place) })
dict[nil] = nil
let result = dict as! [UKCountry: [LocalPopulation]]
But that's a bit too "procedural"... What's more annoying is, in all the above attempts, I have used !. I know sometimes ! is unavoidable, but I'd like to avoid it whenever I can.
How can I do this Swiftily?
First, allow dictionaries to easily be created from grouped key-value pairs:
public extension Dictionary {
/// Group key-value pairs by their keys.
///
/// - Parameter pairs: Either `Swift.KeyValuePairs<Key, Self.Value.Element>`
/// or a `Sequence` with the same element type as that.
/// - Returns: `[ KeyValuePairs.Key: [KeyValuePairs.Value] ]`
init<Value, KeyValuePairs: Sequence>(grouping pairs: KeyValuePairs)
where
KeyValuePairs.Element == (key: Key, value: Value),
Self.Value == [Value]
{
self =
Dictionary<Key, [KeyValuePairs.Element]>(grouping: pairs, by: \.key)
.mapValues { $0.map(\.value) }
}
/// Group key-value pairs by their keys.
///
/// - Parameter pairs: Like `Swift.KeyValuePairs<Key, Self.Value.Element>`,
/// but with unlabeled elements.
/// - Returns: `[ KeyValuePairs.Key: [KeyValuePairs.Value] ]`
init<Value, KeyValuePairs: Sequence>(grouping pairs: KeyValuePairs)
where
KeyValuePairs.Element == (Key, Value),
Self.Value == [Value]
{
self.init( grouping: pairs.map { (key: $0, value: $1) } )
}
}
Then, create a key-value sequence that's like KeyValuePairs<Key, Value.Element>, where Key and Value belong to your desired [ UKCountry: [LocalPopulation] ] type – but you don't need to bother with labeled elements.
Dictionary(
grouping: populations.compactMap { population in
UKCountry(placeName: population.place).map { ($0, population) }
}
)
You can use a reduce to transform your [LocalPopulation] into a [UKCountry:[LocalPopulation]] by also filtering out all elements which aren't in the UK.
let dict = populations.reduce(into: [UKCountry:[LocalPopulation]](), { currentResult, population in
if let ukCountry = UKCountry(placeName: population.place) {
currentResult[ukCountry, default: []].append(population)
}
})

Random Elements from Dictionary

Here is my Array of Dictionary,
var myArrayOfDict = [["vegetables": ["CARROT","BEANS"], "fruits": ["APPLE","MANGO","BANANA"], "letters":["A","B","C","D"],"numbers":["ONE","TWO","THREE"],"shapes":["SQUARE","RECTANGLE","CIRCLE"]]]
How do i get the desired output, actually i need to get random selected elements of the specified range ...(i.e) when i need 3 elements randomnly from dictionary as like,
[["fruits": ["APPLE","MANGO","BANANA"],"shapes":["SQUARE","RECTANGLE","CIRCLE"],"numbers":["ONE","TWO","THREE"]]]
When i need just 2 elements randomnly like,
[["shapes":["SQUARE","RECTANGLE","CIRCLE"],"fruits": ["APPLE","MANGO","BANANA"]]]
Thanks in Advance,
Here is one solution using randomElement().
func randomSelection(from dict: [String: [String]], count: Int) -> [String: [String]] {
guard !dict.isEmpty else { return [:] }
var result = [String: [String]]()
for i in 0..<count {
let element = dict.randomElement()! //We know dictionary is not empty
result[element.key] = element.value
}
return result
}
The above solution might return less elements in a dictionary than expected if the same element is returned more than once from randomElemnt(). If this should be voided the below solution should work
func randomSelection(from dict: [String: [String]], count: Int) -> [String: [String]] {
guard !dict.isEmpty else { return [:] }
guard dict.count > count else { return dict }
var result = [String: [String]]()
while result.count < count {
let element = dict.randomElement()!
if result[element.key] == nil {
result[element.key] = element.value
}
}
return result
}
Since the function takes a dictionary as the first argument the array needs to be looped over
for d in myArrayOfDict {
print(randomSelection(from: d, count: 2))
}
Array myArrayOfDict contains a single Dictionary. So, it doesn't make sense getting a random element from it.
As your example explains, you need to get random elements from the Dictionary itself.
So, you can use randomElement to get that working.
let myArrayOfDict = ["vegetables": ["CARROT","BEANS"], "fruits": ["APPLE","MANGO","BANANA"], "letters":["A","B","C","D"],"numbers":["ONE","TWO","THREE"],"shapes":["SQUARE","RECTANGLE","CIRCLE"]]
var elements = [String : [String]]()
let count = 2
for _ in 0..<count {
if let element = myArrayOfDict.randomElement() {
elements[element.key] = element.value
}
}
print(elements)

Reduce `[URLQueryItem]` into `[String: Any]`

Currently I've got this chunky reduce function...
blah: [String: Any] = queryItems.reduce([String: Any]()) {
(params: [String: Any], queryItem: URLQueryItem) in
var output = params
output[queryItem.name] = queryItem.value
return output
}
I'm sure there is a much simpler way of doing this but I can't get my head around how that would work.
Is there a "better" way to do this?
By "better" I mean cleaner, shorter, more elegant, etc...
It's possible to use reduce(into:_:) instead of reduce(_:_). This both save you the lines and the overhead of copying params for each iteration:
let blah: [String: Any] = (urlComponents.queryItems ?? []).reduce(into: [:]) {
params, queryItem in
params[queryItem.name] = queryItem.value
}
This method is preferred over reduce(_:_:) for efficiency when the result is a copy-on-write type, for example an Array or a Dictionary.
You can create a dictionary from the name and value of each query item with
let items = urlComponents.queryItems ?? []
let dict = Dictionary(items.lazy.map { ($0.name, $0.value as Any) },
uniquingKeysWith: { $1 })
In the case of a duplicate name, the later value wins (this can be controlled with the uniquingKeysWith: parameters).
Or remove the as Any cast to get a dictionary of type [String: String?]:
let items = urlComponents.queryItems ?? []
let dict = Dictionary(items.lazy.map { ($0.name, $0.value ) },
uniquingKeysWith: { $1 })
Alternatively
let items = urlComponents.queryItems ?? []
let dict = Dictionary(items.lazy.map { ($0.name, [$0.value] ) },
uniquingKeysWith: +)
to build a dictionary of type [String : [String?]], holding all values for each name.

Swift, Dictionary of String:String, best way to get an array of values, where the key contains a string?

I have a dictionary, like this:
{"Matt":"Apple", "Calvin":"Nut", "Susie":"Pear"}
I want to check the keys of the dictionary with a string, to see if they contain that string-- and if they do, I want to add their respective value to an array
So, if the input was "a"
the return would be ["Apple", "Nut"] because "Matt" and "Calvin" matched the "a"
Basically looking for some Swift tips,
otherwise I was going to implement it like this:
Grab all keys, put into an array
Filter keys for string value, this is the keyArray
Enumerate over keyArray, and get all their values from the dictionary
Boom have an array of values
You can filter the dictionary, using contains() on each key,
and then extract the corresponding values:
let dict = [ "Matt":"Apple", "Calvin":"Nut", "Susie":"Pear" ]
let input = "a"
let values = Array(dict.filter { $0.key.contains(input) }.values)
print(values) // ["Apple", "Nut"]
Or with flatMap() (renamed to compactMap() in Swift 4.1):
let values = dict.flatMap { $0.key.contains(input) ? $0.value : nil }
print(values) // ["Apple", "Nut"]
Here each dictionary entry is mapped to the value if the key contains
the given string, and to nil otherwise. flatMap() then returns an
array with the non-nil values.
Simply using a filter over the dictionary:
let dict: [String: String] = ["Matt": "Apple", "Calvin": "Nut", "Susie": "Pear"]
func findValuesMatching(search: String, in dict: [String: String]) -> [String] {
return dict
.filter { (key, value) in
return key.range(of: search) != nil
}.map { (key, value) in
return value
}
}
print(findValuesMatching(search: "a", in: dict))
Do it like this:
let dict = ["Matt":"Apple", "Calvin":"Nut", "Susie":"Pear"]
let containsA = dict.filter({ $0.key.lowercased().contains("a") }).map({ $0.value })
Output:
["Apple", "Nut"]