Swift dictionary comprehension - swift

Other languages such as Python let you use a dictionary comprehension to make a dict from an array, but I haven't figure out how to do this in Swift. I thought I could use something like this but it doesn't compile:
let x = ["a","b","c"]
let y = x.map( { ($0:"x") })
// expected y to be ["a":"x", "b":"x", "c":"x"]
What is the correct way to generate a dictionary from an array in swift?

The map method simply transforms each element of an array into a new element. The result is, however, still an array. To transform the array into a dictionary you can use the reduce method.
let x = ["a","b","c"]
let y = x.reduce([String: String]()) { (var dict, arrayElem) in
dict[arrayElem] = "this is the value for \(arrayElem)"
return dict
}
This will generate the dictionary
["a": "this is the value for a",
"b": "this is the value for b",
"c": "this is the value for c"]
Some explanation: The first argument of reduce is the initial value which in this case is the empty dictionary [String: String](). The second argument of reduce is a callback for combining each element of the array into the current value. In this case, the current value is the dictionary and we define a new key and value in it for every array element. The modified dictionary also needs to be returned in the callback.
Update: Since the reduce approach can be heavy on memory for large arrays (see comments) you could also define a custom comprehension function similar to the below snippet.
func dictionaryComprehension<T,K,V>(array: [T], map: (T) -> (key: K, value: V)?) -> [K: V] {
var dict = [K: V]()
for element in array {
if let (key, value) = map(element) {
dict[key] = value
}
}
return dict
}
Calling that function would look like this.
let x = ["a","b","c"]
let y = dictionaryComprehension(x) { (element) -> (key: String, value: String)? in
return (key: element, value: "this is the value for \(element)")
}
Update 2: Instead of a custom function you could also define an extension on Array which would make the code easier to reuse.
extension Array {
func toDict<K,V>(map: (T) -> (key: K, value: V)?) -> [K: V] {
var dict = [K: V]()
for element in self {
if let (key, value) = map(element) {
dict[key] = value
}
}
return dict
}
}
Calling the above would look like this.
let x = ["a","b","c"]
let y = x.toDict { (element) -> (key: String, value: String)? in
return (key: element, value: "this is the value for \(element)")
}

Related

Swift Return Sorted Dictionary based on Value Count

As the title suggests, I'm trying to sort an existing Dictionary of type [Character : Int] based on the value count, with most repeated characters appearing first. The following code returns the error error: cannot convert return expression of type '[(key: Character, value: Int)]' to return type '[Character : Int]. Now, I get that my function is returning an array of Tuples but I can't understand how...
Here's the code:
let str = "the brown fox jumps over the lazy dog"
func characterCount(str: String) -> [Character : Int] {
var results: [Character : Int] = [:]
for char in str {
results[char] = 0
let charCount = str.filter { char == $0 }
results[char] = charCount.count
}
let sortedResults = results.sorted { $0.value < $1.value }
return sortedResults
}
print(characterCount(str: str))
As Sweeper notes, dictionaries are inherently unordered, so you will need to transform the dictionary into something sortable. The simplest option here is an array of tuples
let sorted = str
.reduce(into: [Character: Int]()){ $0[$1] = $0[($1), default: 0] + 1}
.map{($0.key, $0.value)}
.sorted{$0.1 > $1.1}
This answer uses reduce(into:) to create the dictionary from the string, but you could use a for loop if you prefer. It then maps the dictionary into an array of tuples of (Character, Int) where the Character is the dictionary key (i.e. the string character) and the Int is it's value (i.e. the character's count), and sorts that based on the value of the count.

How to convert Array to Dictionary while keeping the same order Swift

Hey I have been looking at some really good question on here. About how to convert a Array to a dictionary but the problem is that is doesn't keep the same order Example:
list = ["test", "test2", "test3"]
outPut:
listNewFromExt = ["test": "test","test3": "test3", "test2": "test2"]
Basically test3 is being switched places with test2
Code:
let listNewFromExt = list.toDictionary{($0, $0)}
extension Sequence {
public func toDictionary<K: Hashable, V>(_ selector: (Iterator.Element) throws -> (K, V)?) rethrows -> [K: V] {
var dict = [K: V]()
for element in self {
if let (key, value) = try selector(element) {
dict[key] = value
}
}
return dict
}
}
Also if you could tell me how to just make the .values "nil" instead of a copy of the key that would be great lol.
In Swift, a Dictionary is an unordered collection by design. Therefore you can't keep any order to it after migrating from an Array.
If you want your values to be nil, just use
let dict = Dictionary<Int, Any?>(uniqueKeysWithValues: [1, 2, 3].map { ($0, nil) })
This evaluates to [2: nil, 3: nil, 1: nil]
If you want some sort of sorting (no pun intended), you may convert the dict to a sorted tuple array:
let sortedTupleArray = dict.sorted { $0.key > $1.key }
This evaluates to [(key: 3, value: nil), (key: 2, value: nil), (key: 1, value: nil)]

How to check if value type is Dictionary<String, Any>

I created a function in Swift that loop through a dynamic dictionary, however when I try to check if the value is a Dictionary type the type comparison condition always fails, in fact XCode raises the following warning as hint:
Cast from '(key: String, value: Any)' to unrelated type 'Dictionary<String, Any>' always fails.
I am not trying to cast any value, I just want to check if the variable value has the type Dictionary.
This is my code:
func readNode(node: Dictionary<String, Any>, level: Int)
{
// Print spaces
for _ in 0 ... level
{
print(" ")
}
for (key, val) in node.enumerated()
{
// The following condition is always false (here is the issue)
if val is Dictionary<String, Any> {
print("Key \(key):")
readNode(node: val, level: (level + 1) * 2)
}
else
{
print("Key \(key): \(val)")
}
}
}
var mydict = Dictionary<String, Any>()
mydict = ["subindex2": 2, "subindex3": 3, "subindex4": ["whatever": "extra"]]
readNode(node: mydict, level: 0)
I am using Swift 3.0.1.
enumerated() creates a sequence of consecutive Ints starting from 0, paired with the elements of the sequence you call it on. That's not what you want – you just want to iterate over a given dictionary's key-value pairs. So just remove .enumerated(), and iterate over the dictionary directly.
You'll also want to employ conditional type-casting rather than a simple is check, allowing val to be statically typed as [String : Any] in the success branch (otherwise you won't be able to pass it back into readNode(node:level:)).
// ...
for (key, val) in node {
if let val = val as? [String : Any] {
print("Key \(key):")
readNode(node: val, level: (level + 1) * 2)
} else {
print("Key \(key): \(val)")
}
}
// ...

Casting Any to Array

Here's the code I try to make work
struct A {
var x:Int = 0
}
struct B {
var y:Int = 0
}
var c: [String:Any] = [
"a":[A()],
"b":[B()]
]
for (key, value) in c {
let arr = value as! [Any]
}
It just throws exception. The runtime exception is raised when trying to cast Any to [Any].
The main thing I want to achieve is iterate through the elements of Any, if Any is array. To me it was natural to cast Any to [Any], but for some reason it doesn't work. So how can I do this in obvious thing in swift?
I saw some workarounds to cast Any to [A] or [B], but that's not my case, because the array can contain just an arbitrary struct.
You can make use of runtime introspection to inspect whether values in your dictionary are of collection type, and if so, iterate over their children (= elements, for array case), and append these to an actual array of Any, in so letting Swift know some Any values in your dictionary are actually arrays.
/* Example setup */
struct A {
var x: Int
init(_ x: Int) { self.x = x }
}
struct B {
var y: Int
init(_ y: Int) { self.y = y }
}
var c: [String:Any] = [
"a": [A(1), A(2)],
"b": [B(3)],
"c": "JustAString",
"d": A(0)
]
E.g. as follows
/* runtime introspection to extract array values from dictionary */
var commonAnyArr: [[Any]] = []
for (_, value) in c {
if case let m = Mirror(reflecting: value)
where (m.displayStyle ?? .Struct) == .Collection {
let arr = m.children.map { $0.value }
commonAnyArr.append(arr)
}
}
/* resulting array of any arrs, that Swift now recognize as actual arrays */
commonAnyArr.forEach { print($0) }
/* [B(y: 3)]
[A(x: 1), A(x: 2)] */
commonAnyArr.flatten().forEach { print($0) }
/* B(y: 3)
A(x: 1)
A(x: 2) */
Alternatively, use the runtime introspection to construct a new dictionary, containing only the key-value pairs of c where the underlying value wrapped by the Any value is in fact an array (however in the new dictionary explicitly specifying for swift that the values are arrays of Any).
/* runtime introspection to extract array values from dictionary */
var dictOfAnyArrs: [String: [Any]] = [:]
for (key, value) in c {
if case let m = Mirror(reflecting: value)
where (m.displayStyle ?? .Struct) == .Collection {
let arr = m.children.map { $0.value }
dictOfAnyArrs[key] = arr
}
}
/* "remaining" dictionary keys now only with [Arr] values */
for (key, arr) in dictOfAnyArrs {
for element in arr {
print("Do something with element \(element)")
}
print("---")
}
/* Do something with element B(y: 3)
---
Do something with element A(x: 1)
Do something with element A(x: 2)
--- */
Just note that the above could be considered somewhat "hacky" (in the eyes of Swift and its pride in static typing and runtime safety), and possibly mostly interesting more out of a technical aspect rather than to be used in actual production code (I would personally never allow anything like the above in production of my own). Perhaps if you take a step back and look at how you've reached this issue, you could re-work your code and design to not reach a point where you need to resort to runtime hacks.

Convert dictionary values to a different type in Swift

I have have a dictionary. I would like to go through it and convert the values to a different type. .map{ } would be perfect except this is a dictionary and not an array. So, I found a mapPairs function on stack overflow that should work for dictionaries. Unfortunately I get a conversion error.
extension Dictionary {
// Since Dictionary conforms to CollectionType, and its Element typealias is a (key, value) tuple, that means you ought to be able to do something like this:
//
// result = dict.map { (key, value) in (key, value.uppercaseString) }
//
// However, that won't actually assign to a Dictionary-typed variable. THE MAP METHOD IS DEFINED TO ALWAYS RETURN AN ARRAY (THE [T]), even for other types like dictionaries. If you write a constructor that'll turn an array of two-tuples into a Dictionary and all will be right with the world:
// Now you can do this:
// result = Dictionary(dict.map { (key, value) in (key, value.uppercaseString) })
//
init(_ pairs: [Element]) {
self.init()
for (k, v) in pairs {
self[k] = v
}
}
// You may even want to write a Dictionary-specific version of map just to avoid explicitly calling the constructor. Here I've also included an implementation of filter:
// let testarr = ["foo" : 1, "bar" : 2]
// let result = testarr.mapPairs { (key, value) in (key, value * 2) }
// result["bar"]
func mapPairs<OutKey: Hashable, OutValue>(#noescape transform: Element throws -> (OutKey, OutValue)) rethrows -> [OutKey: OutValue] {
return Dictionary<OutKey, OutValue>(try map(transform))
}
}
var dict1 = ["a" : 1, "b": 2, "c": 3]
let convertedDict: [String: String] = dict1.mapPairs { // ERROR: cannot convert value of type '_ -> (String, Int)' to expected argument type '(String, Int) -> (String, String)'
element -> (String, Int) in
element[0] = String(element.1)
return element
}
In Swift 5 and later:
let originalDict: [TypeA: TypeB] = /* */
let convertedDict: [TypeA: TypeC] = originalDict.mapValues { /* conversion here */ }
Example:
let integerDict: [String: Int] = ["a": 1, "b": 2]
let doubleDict: [String: Double] = integerDict.mapValues(Double.init)
print(doubleDict) // ["a": 1.0, "b": 2.0]
If you want to change a dict of [String: Int] to [String: String], you can pretty much do the same as my previous answer:
let dict1 = ["a" : 1, "b": 2, "c": 3]
var dict2 = [String: String]()
dict1.forEach { dict2[$0.0] = String($0.1) }
print("\(dict2.dynamicType): \(dict2)")
Output:
Dictionary<String, String>: ["b": "2", "a": "1", "c": "3"]
I don't know if this might help, but since Swift 4.2 there is a new operator called mapValues(_:) (https://developer.apple.com/documentation/swift/dictionary/2995348-mapvalues), which would transform the result you are looking for to:
let convertedDict = dict1.mapValues { String($0) }
As the example given by the method block, you should use mapPairs like this:
let convertedDict: [String: String] = dict1.mapPairs { (key, value) in
(key, String(value))
}
Note, since Swift supports implicit inference, you don't need explicitly return.