Invert keys for values in Swift dictionary, collecting duplicate values - swift

I have the following dictionary:
let dict = ["key1": "v1", "key2": "v1", "key3": "v2"]
I want to swap the values for keys so that the result to be:
result = ["v1": ["key1", "key2"], "v2": ["key3"]]
How can I do this without using for loops (i.e. in a functional way)?

In Swift 4, Dictionary has the init(_:uniquingKeysWith:) initializer that should serve well here.
let d = [1 : "one", 2 : "two", 3 : "three", 30: "three"]
let e = Dictionary(d.map({ ($1, [$0]) }), uniquingKeysWith: {
(old, new) in old + new
})
If you did not have duplicate values in the original dict that needed to be combined, this would be even simpler (using another new initializer):
let d = [1 : "one", 2 : "two", 3 : "three"]
let e = Dictionary(uniqueKeysWithValues: d.map({ ($1, $0) }))

You can use grouping initializer in Swift 4:
let dict = ["key1": "v1", "key2": "v1", "key3": "v2"]
let result = Dictionary(grouping: dict.keys.sorted(), by: { dict[$0]! })
Two notes:
You can remove .sorted() if the order of keys in resulting arrays is not important.
Force unwrap is safe in this case because we're getting an existing dictionary key as the $0 parameter

This is a special application of the commonly-needed ability to group key-value pairs by their keys.
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) } )
}
}
With that, just flip each key-value pair and go to town.
Dictionary( grouping: dict.map { ($0.value, $0.key) } )

Related

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

Swift replace key value in array of dictionaries with nested dictionaries

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

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"]

Pick value stored against a hash key which ends with a specific character

I have a hash of type [String: [String: AnyObject]]() something like below,
{
"asndfkjsndkjnf_1": {"A": "2"},
"jckxjbcvkjbsdsdfkz_2": {"G": 3},
"sahgkshbgskdssjf_3": {"T": '1'},
"asdhlsfldsnfsldk_4": {"C": 4}
}
I want to pick the value of hash entry whose key ends with "_1", since hashes don't have indexes I am finding it a bit difficult to retrieve item I want without knowing the complete key.
Dictionary has a keys property that lets you (lazily) evaluate all the keys of the dictionary, so you can just use:
let foo = [
"asndfkjsndkjnf_1": ["A": "2"],
"jckxjbcvkjbsdsdfkz_2": ["G": 3],
"sahgkshbgskdssjf_3": ["T": "1"],
"asdhlsfldsnfsldk_4": ["C": 4]
]
if let match = foo.keys.filter({ (key) -> Bool in key.hasSuffix("_1") }).first {
// foo[match] will be the first matched dictionary
print("\(foo[match])")
}
another possibility is to just filter the whole dictionary for matching keys:
foo.filter({ $0.0.hasSuffix("_1") }).first?.1
The normal way of fetching a value from a dictionary by using optional binding:
let dict: [String:AnyObject] = [
"asndfkjsndkjnf_1": ["A": "2"],
"jckxjbcvkjbsdsdfkz_2": ["G": 3],
"sahgkshbgskdssjf_3": ["T": "1"],
"asdhlsfldsnfsldk_4": ["C": 4]
]
if let innerDict = dict["asndfkjsndkjnf_1"] as? [String:AnyObject] {
print(innerDict)
if let valueA = innerDict["A"] as? String {
print(valueA)
}
}
But in a comment you say
I don't know the complete key to retrieve the value, I only know that the hash's key ends with a certain pattern
You can use .hasSuffix() to find which key ends with a certain string, then use this key to fetch the inner value.
Let's say you want to find the value for the key ending with "_3":
for key in dict.keys where key.hasSuffix("_3") {
if let innerDict = dict[key] as? [String:AnyObject] {
print(innerDict)
if let innerValue = innerDict["T"] as? String {
print(innerValue)
}
}
}

Sort Dictionary by Key Value

let dict: [String:Int] = ["apple":5, "pear":9, "grape":1]
How do you sort the dictionary based on the Int value so that the output is:
sortedDict = ["pear":9, "apple":5, "grape":1]
Current Attempt (doesn't sort correctly):
let sortedDict = sorted(dict) { $0.1 > $1.1 }
You need to sort your dictionary values, not your keys. You can create an array of tuples from your dictionary sorting it by its values as follow:
Xcode 9 • Swift 4 or Xcode 8 • Swift 3
let fruitsDict = ["apple": 5, "pear": 9, "grape": 1]
let fruitsTupleArray = fruitsDict.sorted{ $0.value > $1.value }
fruitsTupleArray // [(.0 "pear", .1 9), (.0 "apple", .1 5), (.0 "grape", .1 1)]
for (fruit,votes) in fruitsTupleArray {
print(fruit,votes)
}
fruitsTupleArray.first?.key // "pear"
fruitsTupleArray.first?.value // 9
To sort your dictionary using your keys
let fruitsTupleArray = fruitsDict.sorted{ $0.key > $1.key }
fruitsTupleArray // [(key "pear", value 9), (key "grape", value 1), (key "apple", value 5)]
To sort your dictionary using its keys and localized comparison:
let fruitsTupleArray = fruitsDict.sorted { $0.key.localizedCompare($1.key) == .orderedAscending }
edit/update:
We can also extend Sequence protocol and implement a custom sort that takes a predicate and sort using a keypath property as long as it conforms to Comparable:
extension Sequence {
func sorted<T: Comparable>(_ predicate: (Element) -> T, by areInIncreasingOrder: ((T,T)-> Bool) = (<)) -> [Element] {
sorted(by: { areInIncreasingOrder(predicate($0), predicate($1)) })
}
}
Usage:
let sortedFruitsAscending = fruitsDict.sorted(\.value)
print(sortedFruitsAscending)
let sortedFruitsDescending = fruitsDict.sorted(\.value, by: >)
print(sortedFruitsDescending)
This will print
[(key: "grape", value: 1), (key: "apple", value: 5), (key: "pear", value: 9)]
[(key: "pear", value: 9), (key: "apple", value: 5), (key: "grape", value: 1)]
edit/update:
For Xcode 13 or later you can use a new generic structure called KeyPathComparator:
let fruitsTupleArray = fruitsDict.sorted(using: KeyPathComparator(\.value, order: .reverse))
Dictionaries can't be sorted. Generally, when I need things sorted from a dictionary, I will make a separate array of my dictionary keys.
In your case, make an array of the keys, sort them by comparing their values in the dictionary.
It can be achieved by using the below implementation
let sortedDictionary = unsortedDictionary.sorted{$0.key > $1.key}
use KeyValuePairs instead of Dictionary
"The order of key-value pairs in a dictionary is stable between mutations but is otherwise unpredictable. If you need an ordered collection of key-value pairs and don’t need the fast key lookup that Dictionary provides, see the KeyValuePairs type for an alternative." - Swift Dictionary