How to compare array of dictionaries? - swift

I have an array of dictionaries whose keys could vary depending on the incoming data. For example,
arrayOfDict = [["default": "Accessibility", "cn": "为每个人而设计"],
["br": "Acessibilidade", "hk_cn": "輔助使用", "default": "Accessibility"],
["hk_cn": "輔助", "default": "Accessibility", "pl": "Ułatwienia dostępu"]]
I want to loop through each of the dictionaries to find if any key has multiple values and display that multiple values to the user and then let the user choose one of the value, remaining values has to be deleted.(For example in the above array "hk_cn" has two different values)
And if the multiple values are same, I want to delete one of the key. (For ex: default has same value everywhere)
So finally, I should have a single dictionary from arrayOfDict.
So final result should be : if user has chosen first "hk_cn" value.
["default": "Accessibility", "cn": "为每个人而设计","hk_cn": "輔助使用", "br": "Acessibilidade", "pl": "Ułatwienia dostępu" ]

1. Flatten out the arrayOfDict to get a single Dictionary using flatMap(_:)
let combinedDict = arrayOfDict.flatMap{($0)}
2. Group the combinedDict element based on the key using init(grouping:by:)
let groupedDict = Dictionary(grouping: combinedDict) { $0.key }
3. Get the formattedDict using mapValues(_:)
let formattedDict = groupedDict.mapValues { Array(Set($0.map { $0.value })) }
formattedDict Output:
["pl": ["Ułatwienia dostępu"], "cn": ["为每个人而设计"], "br": ["Acessibilidade"], "default": ["Accessibility"], "hk_cn": ["輔助使用", "輔助"]]
Now you can prompt user using the formattedDict and ask for which value to keep for the respective keys.
Edit:
As per your comment, you can use allSatisfy(_:) on formattedDict to check if it contains single value for each key.
let formattedDict = groupedDict.mapValues { Set($0.map { $0.value }) }
if formattedDict.values.allSatisfy({ $0.count == 1 }) {
let newformattedDict = formattedDict.compactMapValues { $0.first }
} else {
//add your code to display data in table here...
}

Related

UserDefaults How can I get my own saved keys?

I tried this code but it brings me all keys saved except me. How can I get my own saved keys?
print("UD: \(UserDefaults.standard.dictionaryRepresentation().keys) \n")
Console:
The key I saved is "Ağustos Test 1".
How can I get only this key?
You cannot "get" from user defaults just the keys for user defaults entries that you created in code. What's in your user defaults is what's in your user defaults; it doesn't have any way of distinguishing "who" created a particular entry.
Knowing the keys you added is your business. Typically this information is hard-coded into your app, e.g. you have a list of constants, usually as static properties of an enum or struct. If you are creating keys dynamically, then if you need to know the names of the keys you created, storing that information is entirely up to you.
IDEALLY you would know the key or group all your keys as a child of another key but thats not what you asked for :)
A few workarounds:
1. all:
you COULD enumerate all keys in userDefaults BUT you have to be aware you'll get keys, that aren't yours...
let dict = UserDefaults.standard.dictionaryRepresentation()
for key in dict.keys {
if let value = dict[key] {
print("\(key) = \(value)")
}
}
This will print all that is in there and that includes almost a hundred apple config values. so.. no good!
2. inclusive filter
if your keys have a commonality, you could invert the filter:
import Foundation
let included_prefixes = ["myprefs.", "myprefs2."]
//my keys
UserDefaults.standard.set(1, forKey: "myprefs.int1")
UserDefaults.standard.set("str1", forKey: "myprefs2.str1")
let dict = UserDefaults.standard.dictionaryRepresentation()
let keys = dict.keys.filter { key in
for prefix in included_prefixes {
if key.hasPrefix(prefix) {
return true
}
}
return false
}
for key in keys {
if let value = dict[key] {
print("\(key) = \(value)")
}
}
3. fragile exclusive filter
So if you really dont know your keys you COULD filter them out
import Foundation
let blacklisted_prefixes = ["Country", "NS", "com.apple", "com.omnigroup", "NavPanel", "WebAutomatic", "NSTableViewDefaultSizeMode", "sks_agent", "Apple", "PayloadUUID", "PKSecure", "_HI", "AK", "ContextMenu", "MultipleSession", "CSUI"]
//my keys
UserDefaults.standard.set(1, forKey: "int1")
UserDefaults.standard.set("str1", forKey: "str1")
let dict = UserDefaults.standard.dictionaryRepresentation()
let keys = dict.keys.filter { key in
for prefix in blacklisted_prefixes {
if key.hasPrefix(prefix) {
return false
}
}
return true
}
for key in keys {
if let value = dict[key] {
print("\(key) = \(value)")
}
}
BUT this is very fragile and not really advisable!
#needsmust

Removing Non Duplicate Keys from Two Dictionary

I have two dictionaries in Swift with few similar values which are in dynamic mode:
dict1 = ["a1":"value 1", "b1":"value2", "c1":"value 3"]
dict2 = ["b1": "value2", "d1": "value4"]
If I want to compare these two dictionaries and want to extract only the matching keys even nested, how do I about to do that?
If you want the common keys with the value in one of them :
let intersectionDict = dict1.filter { dict2.keys.contains($0.key) }
//Or
let intersectionDict2 = dict2.filter { dict1.keys.contains($0.key) }
If you want the values to match too:
let intersectionDict3 = dict1.filter { dict2[$0.key] == $0.value }
And the result is:
print(intersectionDict3) //["b1": "value2"]
As others have shown, you can do this using a filter statement. You can make it even quicker by always filtering the smaller of the two dicts, improving the time complexity from O(dict1.size) to O(min(dict1.size, dict2.size).
extension Dictionary {
func intersectingByKeys(with other: Dictionary) -> Dictionary {
let (smallerDict, largerDict) = (self.count < other.count) ? (self, other) : (other, self)
return smallerDict.filter { key, _ in largerDict.keys.contains(key) }
}
}
let dict1 = ["a1":"value 1", "b1":"value2", "c1":"value 3"]
let dict2 = ["b1": "value2", "d1": "value4"]
print(dict1.intersectingByKeys(with: dict2))
You can create a Set from the keys of one of the dictionaries and call intersection on the Set with the keys of the other dictionary.
let matchingKeys = Set(dict1.keys).intersection(dict2.keys) // {"b1"}

How to extract a subset of a swift 3 Dictionary

I've looked through the methods here but I can't quite find what I'm looking for. I'm new-ish to Swift. I would like to extract a subset from a Dictionary based on a Set of key values, preferably without a loop.
For example, if my key Set is of type Set<String> and I have a Dictionary of type Dictionary<String, CustomObject>, I would like to create a new Dictionary of type Dictionary<String, CustomObject> that contains only the key-value pairs associated with the keys in the Set of Strings.
I can see that I could do this with for loop, by initializing a new Dictionary<String, CustomObj>(), checking if the original Dictionary contains a value at each String in the set, and adding key-value pairs to the new Dictionary. I am wondering if there is a more efficient/elegant way to do this however.
I'd be open to finding the subset with an Array of Strings instead of a Set if there is a better way to do it with an Array of keys.
Many thanks!
Swift 5 - You can do this very simply:
let subsetDict = originalDict.filter({ mySet.contains($0.key)})
The result is a new dictionary with the same type as the original but which only contains the key-value pairs corresponding to the keys in mySet.
Your assumption is correct, there is a more concise/swift-ish way to accomplish what you need.
For example you can do it via reduce, a functional programming concept available in Swift:
let subDict = originalDict.reduce([String: CustomObject]()) {
guard mySet.contains($1.key) else { return $0 }
var d = $0
d[$1.key] = $1.value
return d
}
Or, in two steps, first filtering the valid elements, and then constructing back the dictionary with the filtered elements:
let filteredDict = originalDict.filter { mySet.contains($0.key) }
.reduce([CustomObject]()){ var d = $0; d[$1.key]=$1.value; return d }
forEach can also be used to construct the filtered dictionary:
var filteredDict = [CustomObject]()
mySet.forEach { filteredDict[$0] = originalDict[$0] }
, however the result would be good it it would be immutable:
let filteredDict: [String:CustomObject] = {
var result = [String:CustomObject]()
mySet.forEach { filteredDict2[$0] = originalDict[$0] }
return result
}()
Dummy type:
struct CustomObject {
let foo: Int
init(_ foo: Int) { self.foo = foo }
}
In case you'd like to mutate the original dictionary (instead of creating a new one) in an "intersect" manner, based on a given set of keys:
let keySet = Set(["foo", "baz"])
var dict = ["foo": CustomObject(1), "bar": CustomObject(2),
"baz": CustomObject(3), "bax": CustomObject(4)]
Set(dict.keys).subtracting(keySet).forEach { dict.removeValue(forKey: $0) }
print(dict) // ["foo": CustomObject(foo: 1), "baz": CustomObject(foo: 3)]

Swift dictionary all containing

Lets say I have dictionaries like below and wanted an array of red dogs. I figured I need to get an array of all the names of the type "dog" using the first dictionary, and then use the name key and the color to search the final dictionary to get ["Polly,"jake"]. I've tried using loops but can't figure out how to iterate through the dictionary.
var pets = ["Polly" : "dog", "Joey" : "goldfish", "Alex" : "goldfish", "jake" : "dog"]
var petcolor = ["Polly" : "red", "Joey" : "black", "Alex" : "yellow", "jake":red"]
The correct solution would seem to be to create a Pet struct (or class) and collate all of this information into a struct and build either an array or dictionary full of these values.
struct Pet {
let name: String
let type: String
let color: String
init(name: String, type: String, color: String) {
self.name = name
self.type = type
self.color = color
}
}
Now, let's build an array of these pets:
var goodPets = [Pet]()
for (petName, petType) in pets {
guard let petColor = petcolor[petName] else {
// Found this pet's type, but couldn't find its color. Can't add it.
continue
}
goodPets.append(Pet(name: petName, type: petType, color: petColor))
}
Now that we've filled out goodPets, pulling out any particular subset of Pets becomes very easy:
let redDogs = goodPets.filter { $0.type == "dog" && $0.color = "red" }
And although this answer looks like a lot of set up & legwork compared to other answers, the major advantage here is that once we build the goodPets array, any way we want to scoop pets out of there ends up being more efficient. And as we increase the number of properties the pets have, this becomes more and more true compared to the other answers.
If you'd rather store our model objects in a dictionary continuing to use the names as the keys, we can do that as well, but the filter looks a little bit stranger.
Building the dictionary looks mostly the same:
var goodPets = [String : Pet]()
for (petName, petType) in pets {
guard let petColor = petcolor[petName] else {
// Found this pet's type, but couldn't find its color. Can't add it.
continue
}
goodPets[petName] = (Pet(name: petName, type: petType, color: petColor))
}
But the filter is slightly different:
let redDogs = goodPets.filter { $0.1.type = "dog" && $0.1.color = "red" }
Note that in both cases, redDogs has the type [Pet], that is, an array of Pet values.
You can iterate through a dictionary like this:
for key in pets.keys() {
if pets[key] == "Dog" {
}
}
Or:
for (name, pet) in pets {
if pet == "Dog" {
}
}
nhgrif is probably correct about structure but, to answer the literal question:
let dogs = Set(pets.filter { $0.1 == "dog" }.map { $0.0 })
let redAnimals = Set(petscolor.filter { $0.1 == "red" }.map { $0.0 })
let redDogs = dogs.intersect(redAnimals)
Each filter is a block that operates on a (key, value) tuple, testing the value and ultimately creating a dictionary with only the matching (key, value) pairs. Each map then converts that filtered dictionary into an array by discarding the values and just keeping the keys.
Each array is turned into a set to support the intersect operation. The intersect then determines the intersection of the two results.

how to iterate through an NSDictionary in order swift

Is it possible to iterate through an NSDictionary in a specific order so I can save an index for an key-value pair in CoreData, based on the order that the data is originally typed? i.e in the code below Set 1 would have an index of 1, set 2 - 2 and set 3 - 3 rather than at random, as is normal NSDictionary behaviour? Thanks in advance if anyone can either give a solution, or tell me its not possible!
let string1 = "rain, wait, train".wordsInArray
let string2 = "oil, join, coin".wordsInArray
let string3 = "made, came, same".wordsInArray
let lists: [String: [String]] =
["Set 1: List 1": string1,
"Set 1: List 2": string2,
"Set 1: List 3": string3]
var index = 0
For list in lists {
list.listIndex = index
index = index + 1
coreDataStack.saveMainContext()
}
extension String {
var wordsInArray:[String] {
return componentsSeparatedByCharactersInSet(NSCharacterSet.punctuationCharacterSet()).joinWithSeparator("").componentsSeparatedByString(" ")
}
In your example, your keys happen to be added in alphanumeric order. This could be fortuitous but if you meant to get the data in key sorted order, that is not the same request as creation order and it would be easy to do:
for (key,wordlist) in lists.sort({$0.0 < $1.0})
{
// you will be getting the dictionary entries in key order
}
// trickier to access by index though
let aKey = lists.keys.sort()[2]
let aWordList = lists[aKey]
// but it lets you get the wordlist from the key
let S1L3Words = lists["Set 1: List 3"]
On the other hand, if you're meaning to ONLY use a creation order, and do not need to access the elements by their keys, you could declare your structure as an array of tuples:
let lists: [(String, [String])] =
[
("Set 1: List 1", string1),
("Set 1: List 2", string2),
("Set 1: List 3", string3)
]
// your for loop will get them in the same order
for (key,wordlist) in lists
{
// array order, no matter what the key values are
}
// also accessible directly by index
let (aKey, aWordList) = lists[2] // ("Set 1: List 3", ["made", "came", "same"])
And finally, if you don't need keys at all, you can just make it an array of arrays:
let lists: [[String]] = [ string1 , string2 , string3 ]
for (key,wordlist) in lists
{
// array order
}
// also accessible directly by index
let aWordList = lists[2] // ["made", "came", "same"]