Sort array of dictionaries based on key and value - swift

Been playing around the high order functions lately,
and i thought of a case and i don't know if this is even possible,
consider the case below.
StringDictionary - typealias StringDictionary = [String: String]
var Group: [StringDictionary] = [
["Key1":"val2"],
["Key4":"val4"],
["Key3":"val3"],
["Key5":"val5"],
["Key2":"val1"],
]
I want to reorder this array of dictionaries to be like this.
Expected Result
var Result = [
["Key1":"val1"],
["Key2":"val2"],
["Key3":"val3"],
["Key4":"val4"],
["Key5":"val5"],
]
Using High order functions

typealias StringDictionary = [String: String]
var Group: [StringDictionary] = [
["Key1":"val2"],
["Key4":"val4"],
["Key3":"val3"],
["Key5":"val5"],
["Key2":"val1"],
]
let keys = Group.map { Array($0.keys) }.reduce([String]()) { $0 + $1 }.sorted()
let values = Group.map { Array($0.values) }.reduce([String]()) { $0 + $1 }.sorted()
let dict = Dictionary(uniqueKeysWithValues: zip(keys, values))
let newGroup:[StringDictionary] = dict.map { [$0:$1] }.sorted{ $0.keys.first! < $1.keys.first! }
print(newGroup)

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

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: Convert Array of Dictionaries to Array of String based on a key

Following is my Array of Dictionaries and I want to get an Array of only strings based on particular key (contentURL key in my case).
How can I achieve it? I have came across Reduce & Filter but no one fits into my requirement.
(
{
contentURL = "https://d1shcqlf263trc.cloudfront.net/1510232473240ab.mp4";
},
{
contentURL = "https://d1shcqlf263trc.cloudfront.net/151021804847312.mp4";
},
{
contentURL = "https://d1shcqlf263trc.cloudfront.net/151021536556612.mp4";
},
{
contentURL = "https://d1shcqlf263trc.cloudfront.net/151021528690312.mp4";
}
)
Expected Output
[
"https://d1shcqlf263trc.cloudfront.net/1510232473240ab.mp4",
"https://d1shcqlf263trc.cloudfront.net/151021804847312.mp4",
"https://d1shcqlf263trc.cloudfront.net/151021536556612.mp4",
"https://d1shcqlf263trc.cloudfront.net/151021528690312.mp4"
]
Just use compactMap
let array = arrayOfDicts.compactMap {$0["contentURL"] }
var myDict: [[String : String]] = [["contentURL" : "https://d1shcqlf263trc.cloudfront.net/1510232473240ab.mp4"],["contentURL" : "https://d1shcqlf263trc.cloudfront.net/1510232473240ab.mp4"],["contentURL" : "https://d1shcqlf263trc.cloudfront.net/1510232473240ab.mp4"]]
let arr = myDict.map { $0["contentURL"] }
var stringArray:[String] = []
for (key, value) in yourArrayOfDictionary {
stringArray.append(value)
}
var arrayDict = [["contentURL":"fd"],["contentURL":"fda"],["contentURL":"fdb"],["contentURL":"fdc"]]
let arraywithOptionstring = arrayDict.map{$0["contentURL"]}
if let arr = arraywithOptionstring as? [String]{
print(arr)
}
Expected Output : ["fd", "fda", "fdb", "fdc"]
If you want to use reduce:
let arr = [
["contentURL" : "https://d1shcqlf263trc.cloudfront.net/"],
["contentURL" : "https://d1shcqlf263trc.cloudfront.net/.mp4"],
["contentURL" : "https://d1shcqlf263trc.cloudfront.net/1510232473240ab.mp4"]
]
let only = arr.reduce([String]()) { (partialRes, dictionary) -> [String] in
return partialRes + [dictionary["contentURL"]!]
}
More compact version:
let compact = arr.reduce([String]()) { $0 + [$1["contentURL"]!] }
Probably you weren't able to use reduce since you need to remember that subscripting a dictionary returns an Optional that is a different type than String
Also you can use just .map in this case.
let array = arrayOfDicts.map {$0["contentURL"]! }

Swiftier Swift for 'add to array, or create if not there...'

I've noticed a common pattern in Swift is
var x:[String:[Thing]] = [:]
so, when you want to "add an item to one of the arrays", you can not just
x[which].append(t)
you have to
if x.index(forKey: which) == nil {
x[which] = []
}
x[which]!.append(s!)
Really, is there a swiftier way to say something like
x[index?!?!].append??(s?!)
While this is a question about style, performance seems to be a critical issue when touching arrays in Swift, due to the copy-wise nature of Swift.
(Please note, obviously you can use an extension for this; it's a question about Swiftiness.)
Swift 4 update:
As of Swift 4, dictionaries have a subscript(_:default:) method, so that
dict[key, default: []].append(newElement)
appends to the already present array, or to an empty array. Example:
var dict: [String: [Int]] = [:]
print(dict["foo"]) // nil
dict["foo", default: []].append(1)
print(dict["foo"]) // Optional([1])
dict["foo", default: []].append(2)
print(dict["foo"]) // Optional([1, 2])
As of Swift 4.1 (currently in beta) this is also fast,
compare Hamish's comment here.
Previous answer for Swift <= 3: There is – as far as I know – no way to "create or update" a dictionary
value with a single subscript call.
In addition to what you wrote, you can use the nil-coalescing operator
dict[key] = (dict[key] ?? []) + [elem]
or optional chaining (which returns nil if the append operation
could not be performed):
if dict[key]?.append(elem) == nil {
dict[key] = [elem]
}
As mentioned in SE-0154 Provide Custom Collections for Dictionary Keys and Values and also by #Hamish in the comments, both methods
make a copy of the array.
With the implementation of SE-0154 you will be able to mutate
a dictionary value without making a copy:
if let i = dict.index(forKey: key) {
dict.values[i].append(elem)
} else {
dict[key] = [key]
}
At present, the most efficient solution is given by Rob Napier
in Dictionary in Swift with Mutable Array as value is performing very slow? How to optimize or construct properly?:
var array = dict.removeValue(forKey: key) ?? []
array.append(elem)
dict[key] = array
A simple benchmark confirms that "Rob's method" is the fastest:
let numKeys = 1000
let numElements = 1000
do {
var dict: [Int: [Int]] = [:]
let start = Date()
for key in 1...numKeys {
for elem in 1...numElements {
if dict.index(forKey: key) == nil {
dict[key] = []
}
dict[key]!.append(elem)
}
}
let end = Date()
print("Your method:", end.timeIntervalSince(start))
}
do {
var dict: [Int: [Int]] = [:]
let start = Date()
for key in 1...numKeys {
for elem in 1...numElements {
dict[key] = (dict[key] ?? []) + [elem]
}
}
let end = Date()
print("Nil coalescing:", end.timeIntervalSince(start))
}
do {
var dict: [Int: [Int]] = [:]
let start = Date()
for key in 1...numKeys {
for elem in 1...numElements {
if dict[key]?.append(elem) == nil {
dict[key] = [elem]
}
}
}
let end = Date()
print("Optional chaining", end.timeIntervalSince(start))
}
do {
var dict: [Int: [Int]] = [:]
let start = Date()
for key in 1...numKeys {
for elem in 1...numElements {
var array = dict.removeValue(forKey: key) ?? []
array.append(elem)
dict[key] = array
}
}
let end = Date()
print("Remove and add:", end.timeIntervalSince(start))
}
Results (on a 1.2 GHz Intel Core m5 MacBook) for 1000 keys/1000 elements:
Your method: 0.470084965229034
Nil coalescing: 0.460215032100677
Optional chaining 0.397282958030701
Remove and add: 0.160293996334076
And for 1000 keys/10,000 elements:
Your method: 14.6810429692268
Nil coalescing: 15.1537700295448
Optional chaining 14.4717089533806
Remove and add: 1.54668599367142