Swift dictionary map - init in closure - ios

I have Swift dictionary:
private var params = [String : AnyObject]()
This contains query items like:
"lat" = "40"
"lon" = "100"
I would like to map this dictionary to NSURLQueryItem array. I want to make it "swifty":
params.map{NSURLQueryItem.init}
But I get an error. Even if I change the map to be [String:String?]. I know I can do something like this elegant one-liner. Can anybody tell how?

You just need to add a failable initializer to NSURLQueryItem that receives a tuple like this (String, AnyObject)
extension NSURLQueryItem {
convenience init?(tuple: (String, AnyObject)) {
guard let value = tuple.1 as? String else { return nil }
self.init(name: tuple.0, value: value)
}
}
That's it!
let params: [String:AnyObject] = ["lat": "40", "lon": "100"]
let queryItems = params.flatMap(NSURLQueryItem.init)

Does your value for the dictionary need to be an optional? In a dictionary, when you assign its key as nil, the entry is deleted.
var params = [String:String?]()
params["lat"] = "40"
params["lon"] = "100"
params["key"] = "hey"
print(params) //result: ["lat": Optional("40"), "lon": Optional("100"), "key": Optional("hey")]
params["key"] = nil
print(params) //result: ["lat": Optional("40"), "lon": Optional("100")]
I suggest using a non optional-value dictionary. I have successfully written the code below:
import UIKit
var params = [String:String]()
params["lat"] = "40"
params["lon"] = "100"
let nsurl = params.map() {NSURLQueryItem.init(name: $0, value: $1)}
print(nsurl)
//Result:
//[<NSURLQueryItem 0x7f8252d29730> {name = lat, value = 40}, <NSURLQueryItem 0x7f8252d29700> {name = lon, value = 100}]
I hope this helps

To you can create one expression like this:
(1...100).map(String.init)
The class has to support it, the String has one initializer with the following signature:
public init(stringInterpolationSegment expr: Int)
With allow it to you use the String.init referred as Int -> String.
But in your case the NSURLQueryItem has not the desired initializer, so the more close you can do it is using map like in the conventional way and passing the parameters to the init of the class NSURLQueryItem like in this way:
let listMapped = params.map { NSURLQueryItem(name: $0.0, value: $0.1 as? String) }
I hope this help you.

I looked at What's the cleanest way of applying map() to a dictionary in Swift? and came up with these two answers:
var params = ["lat": "40", "lon":"100"]
var p:[NSURLQueryItem] = []
var result1 = params.map { (key, value) in p.append(NSURLQueryItem(name:key, value:value)) } // version 1
var result2 = params.reduce([NSURLQueryItem]()) { $0 + [NSURLQueryItem(name:$1.0, value:$1.1)] } // version 2
In version 1, the parameter passed by map() is a (String, String) tuple. In version 2, the parameters passed by reduce() are [NSURLQueryItem] and a (String, String) tuple

Firstly, the the block or closure you're providing to the map function isn't quite right. Blocks are basically nameless functions, they take some number of parameters and return some type. If we were to be verbose, a solution would look something like this:
params.map { (a: (String, String)) -> NSURLQueryItem in
return NSURLQueryItem(name: a.0, value: a.1)
}
However we can simplify this bit of code. The dictionary is [String : String] so it can be inferred that the map function will take a (String, String) as a parameter, so we don't need to write that explicitly.
Swift also allows $n to refer to the nth element of a tuple parameter. Since we only have 1 parameter, $0 will refer to the first element of the first parameter.
The return type of the block can also be inferred, since we're creating a NSURLQueryItem, meaning we don't need to specify that either. This means we also don't need to write return in the block, we can just create the object.
Lastly, you should not call NSURLQueryItem.init() to create a new NSURLQueryItem, you should instead just say NSURLQueryItem().
Here's the minimal solution:
params.map { NSURLQueryItem(name: $0, value: $1) }

Related

How to use Swift's higher order functions for parsing dynamic dictionary data in swift?

I am trying to parse the following json and want to retrieve the "key" of a dictionary whose value matches with the given value.
{ "OuterArrayHolder" :
[
{
"dictDynamicKey" : ["dynamicValue1", "dynamicValue2", "dynamicValue3"]
},
{
"dictAnotherDynamicKey" : ["dynamicValue4", "dynamicValue5", "dynamicValue6"]
},
]
}
[Note: Here in above json, all the keys and values are dynamic except "OuterArrayHolder".]
I have implemented it in a non-Swifty way and currently getting the expected output, but I am not getting how to accomplish the same behaviour using swift's higher-order functions.
Input : "dynamicValue2"
Expected output : "dictDynamicKey"
Current solution:
let inputValue = "dynamicValue2"
if !outerArrayHolder.isEmpty {
for dynamicDict in outerArrayHolder {
for (key, value) in dynamicDict {
if value.empty || !value.contains(inputValue) {
continue
} else {
//here if inputValue matches in contianed array (value is array in dictionary) then I want to use its "repective key" for further businisess logic.
}
}
}
}
I want to reduce these two for loops and want to use higher-order functions to achieve the exact behavior, Any help in this regard is really appreciated.
Can we convert your algorithm to a functional style? Yes. Is it a good idea? Probably not in this case. But here's how.
You didn't give any type information, so I'll use this type:
let outerArrayHolder: [[String: Any]] = [
[
"dictDynamicKey": ["dynamicValue1", "dynamicValue2", "dynamicValue3"]
],
[
"dictAnotherDynamicKey" : ["dynamicValue4", "dynamicValue5", "dynamicValue6"]
],
]
And you want to find the key corresponding to the array that contains inputValue:
let inputValue = "dynamicValue2"
The functional strategy is to map each dictionary in outerArrayHolder to its first key that has a matching value. If a dictionary has no such key, the dictionary is mapped to nil. Then we throw away the nils and take the first remaining value.
We can do it with filter, as requested:
let key = outerArrayHolder.lazy
.compactMap {
$0.lazy
.filter { ($0.value as? [String])?.contains(inputValue) ?? false }
.map { $0.key }
.first }
.first
But we can save a lazy and a first using first(where:):
let key = outerArrayHolder.lazy
.compactMap({
$0
.first(where: { ($0.value as? [String])?.contains(inputValue) ?? false })
.map { $0.key }
}).first
I don't see what this has to do with higher-order functions. If the outer key is known, I would simply write
// just building your structure
let d1 = ["dictDynamicKey" : ["dynamicValue1", "dynamicValue2", "dynamicValue3"]]
let d2 = ["dictAnotherDynamicKey" : ["dynamicValue4", "dynamicValue5", "dynamicValue6"]]
let d = ["OuterArrayHolder" : [d1, d2]]
// this is the actual code:
func find(_ target:String) -> String? {
for dict in d["OuterArrayHolder"]! {
for (k,v) in dict {
if v.contains(target) {return k}
}
}
return nil
}
That's just what you're doing, only it's clean.
There's no higher order function that does precisely what you're looking for. The closest is first(where:), but the problem is that the result is just Bool, and you don't have a way to cleanly fish out data related to the found case.
You could write something like:
extension Sequence {
func findFirst<T>(where predicate: (Element) throws -> T?) rethrows -> T? {
for element in self {
if let result = try predicate(element) {
return result
}
}
return nil
}
}
and then use it like:
let dictionaries = [
[
"dictDynamicKey" : ["dynamicValue1", "dynamicValue2", "dynamicValue3"]
],
[
"dictAnotherDynamicKey" : ["dynamicValue4", "dynamicValue5", "dynamicValue6"]
],
]
let desiredValue = "dynamicValue2"
extension Sequence {
func findFirst<T>(where predicate: (Element) throws -> T?) rethrows -> T? {
for element in self {
if let result = try predicate(element) {
return result
}
}
return nil
}
}
let result = dictionaries.findFirst(where: { dict in
dict.findFirst(where: { key, values in
values.contains(desiredValue) ? key : nil
})
})
print(result as Any) // => Optional("dictDynamicKey")
But it's probably more complexity than it's probably worth. I would recommend Matt's solution.
Scaled solution
You haven't clarified on this, but I suspect that you probably need to do this a bunch of times. In that case, linear searching through this gets really slow. By searching for keys by their values, you're not taking advantage of the key benefit of dictionaries: constant-time access to a value by its key. Your code is:
Linear-searching through the array of dictionaries, introduces an O(dictionaries.count) factor
For each dict in the array in #1, linear-searching through the key/value pairs, which introduces a O(dict.count) factor
For each key/value pair in the dict in #2, linear-searching through array of values, which introduces a O(valueArray.count) factor.
The total time complexity multiplies up to O(dictionaries.count * averageDict.count * averageValueArray.count), which gets really slow really quick.
Instead, you can spend some compute cost up-front, to create a new data structure that is better able to service the kinds of queries you want to run on it. In this case, you can "invert" a dictionary.
extension Dictionary {
func inverted<T>() -> [T: Key] where Dictionary.Value == [T] {
let invertedKeyValuePairs = self
.lazy
.flatMap { oldKey, oldValues in
oldValues.map { oldValue in (key: oldValue, value: oldKey) as (T, Key) }
}
return Dictionary<T, Key>(uniqueKeysWithValues: invertedKeyValuePairs)
}
}
// Example usage:
let valuesByKeys = [
"a": [1, 2, 3],
"b": [4, 5, 6]
]
let keysPerValue = valuesByKeys.inverted()
keysPerValue.forEach { key, value in print("key: \(key), value: \(value)") }
// Which results in:
// key: 3, value: a
// key: 4, value: b
// key: 5, value: b
// key: 1, value: a
// key: 6, value: b
// key: 2, value: a
Given such an inverted implementation, you can invert each dict of your input set, and merge them all together:
let invertedDictionary = Dictionary(uniqueKeysWithValues: dictionaries.flatMap { $0.inverted() })
invertedDictionary.forEach { key, value in print("key: \(key), value: \(value)") }
// Result:
key: dynamicValue6, value: dictAnotherDynamicKey
key: dynamicValue1, value: dictDynamicKey
key: dynamicValue2, value: dictDynamicKey
key: dynamicValue3, value: dictDynamicKey
key: dynamicValue4, value: dictAnotherDynamicKey
key: dynamicValue5, value: dictAnotherDynamicKey
You can store and share this dictionary, which can give constant time (O(1)) access to the key that was associated with any desired value:
print(invertedDictionary[desiredValue] as Any) // => Optional("dictDynamicKey")

Adding 'Key:Value' in Multidimensional Dictionary is Overwriting

I declared and initialized a [[String:[String:String] dictionary. It's empty in the beginning, and I am trying to add multiple values under the parent keys.
var dictionary = [String:[String:String]
// some checks
dictionary[i] = ["name" : name]
dictionary[i] = ["from" : country]
dictionary[i] = ["age" : age]
When I do that, I end up having only age key as a child under [key: [String:String]. So it's overwriting when I use this approach.
What is the appropriate way of doing
Your code is creating a new dictionary on each line and assigning this in the dictionary for key i, so you end up with the last dictionary ["age" : age]
You need to create an inner dictionary, assign the values to this and then assign this to your outer dictionary;
var innerDict = [String:String]()
innerDict["name"] = name
innerDict["from"] = from
innerDict["age"] = age
dictionary[i] = innerDict
I would suggest, however, that you look at creating a Struct and put that in your outer dictionary rather than using a dictionary of dictionaries
func insert(key:String, value:String, at k:String) {
var d = dictionary[k] ?? [String:String]()
d[key] = value
dictionary[k] = d
}
And here's how to test it:
insert("name", value: name, at: i)
insert("from", value: country, at: i)
insert("age", value: age, at: i)
You can use optional chaining to assign to the inner dictionary, but you need to create the inner dictionary first.
// create the dictionary of dictionaries
var dictionary = [String:[String:String]]()
// some example constants to make your code work
let i = "abc"
let name = "Fred"
let country = "USA"
let age = "28"
// Use nil coalescing operator ?? to create
// dictionary[i] if it doesn't already exist
dictionary[i] = dictionary[i] ?? [:]
// Use optional chaining to assign to inner dictionary
dictionary[i]?["name"] = name
dictionary[i]?["from"] = country
dictionary[i]?["age"] = age
print(dictionary)
Output:
["abc": ["age": "28", "from": "USA", "name": "Fred"]]
Using these techniques, here's my version of #matt's insert(_:value:at:) function:
func insert(key:String, value:String, at k:String) {
dictionary[k] = dictionary[k] ?? [:]
dictionary[k]?[key] = value
}

String componentsSeparatedByString do one time

I have a string:
let mystring = "key=value=value=value=value"
When i did:
let ar = mystring.componentsSeparatedByString("=")
i get:
["key", "value", "value", "value", "value"]
but i need do split only once, like componentsSeparatedByString("=", 1), to get:
["key", "value=value=value=value"]
With Swift 2.1, you can use the split function as follows to do what you want:
let result = string.characters.split("=", maxSplit: 1, allowEmptySlices: true)
Some example code to test this would be:
let string = "key=value=value=value=value"
let result = string.characters.split("=", maxSplit: 1, allowEmptySlices: true)
print(String(result[0])) // "key"
print(String(result[1])) // "value=value=value=value"
This should do the job
func extract(rawData: String) -> [String]? {
let elms = rawData.characters.split("=", maxSplit: 1).map { String($0) }
guard let
key = elms.first,
value = elms.last
where elms.count == 2 else { return nil }
return [key, value]
}
Example:
let rawData = "key=value=value=value=value"
extract(rawData) // > ["key", "value=value=value=value"]
Please note the extract function does an optional array of strings. Infact if the input string does not contain at least an = then nil is returned.
The code has been tested with the Swift 2.1 and Xcode Playground 7.1.1.
Hope this helps.
You're probably going to have to write your own custom code to do that, using either NSScanner or rangeofString:options:range:
EDIT:
Actually, it sounds like the Swift String class's split function, with its maxSplit parameter, will do what you need. Take a look at the link in Preston's answer.
let mystring = "key=value=value=value=value"
let result = split(mystring as String, { $0 == "=" }, maxSplit: 1, allowEmptySlices: true)
result should now be [key, value=value=value=value]
Thanks for answers, i found working solution for swift2:
let mystring = "key=value=value=value=value"
mystring.characters.split(1, allowEmptySlices: true, isSeparator: { $0 == "=" }).map(String.init)
Try this: (tested and working in playground)
var key = str.substringToIndex(str.rangeOfString("=")!.startIndex)
var value = str.substringFromIndex(str.rangeOfString("=")!.startIndex.advancedBy(1))
var resultingArray = [key, value]

Swift filter array of strings

I've had troubles filtering array of keywords (strings) in swift ,My code:
self.filteredKeywords=filter(keywords.allValues, {(keyword:NSString) ->
Bool in
let words=keyword as? NSString
return words?.containsString(searchText)
})
As AnyObject can't be subtype of NSString, I'm stuck with this!
[Updated for Swift 2.0]
As NSString is toll-free bridged to Swift String, just avoid the coercions with:
3> ["abc", "bcd", "xyz"].filter() { nil != $0.rangeOfString("bc") }
$R1: [String] = 2 values {
[0] = "abc"
[1] = "bcd"
}
But, if you think allValues aren't strings:
(keywords.allValues as? [String]).filter() { nil != $0.rangeOfString("bc") }
which returns an optional array.
Your filter is over [AnyObject], but your closure takes NSString. These need to match. Also, your result needs to be a Bool, not a Bool?. You can address these simply like this:
self.filteredKeywords = filter(keywords.allValues, {
let keyword = $0 as? NSString
return keyword?.containsString(searchText) ?? false
})
This accepts AnyObject and then tries to coerce it down to NSString. It then nil-coalleces (??) the result to make sure it always is a Bool.
I'd recommend, though, treating keywords as a [String:String] rather than an NSDictionary. That would get rid of all the complications of AnyObject. Then you can just do this:
self.filteredKeywords = keywords.values.filter { $0.rangeOfString(searchText) != nil }
Whenever possible, convert Foundation collections into Swift collections as soon as you can and store those. If you have incoming Foundation objects, you can generally convert them easily with techniques like:
let dict = nsdict as? [String:String] ?? [:]
Or you can do the following to convert them such that they'll crash in debug (but silently "work" in release):
func failWith<T>(msg: String, value: T) -> T {
assertionFailure(msg)
return value
}
let dict = nsdict as? [String:String] ?? failWith("Couldn't convert \(d)", [:])
Swift 4.2 provides a new way to do this:
var theBigLebowski = ["The Dude", "Angry Walter", "Maude Lebowski", "Donny Kerabatsos", "The Big Lebowski", "Little Larry Sellers"]
// after removeAll -> ["The Dude", "Angry Walter", "Donny Kerabatsos", "Little Larry Sellers"]
theBigLebowski.removeAll{ $0.contains("Lebowski")}
print(theBigLebowski)
There is both a problem with GoZoner's answer for certain data types and also a slightly better way to do this. The following examples can show this:
let animalArray: NSMutableArray = ["Dog","Cat","Otter","Deer","Rabbit"]
let filteredAnimals = animalArray.filter { $0.rangeOfString("er") != nil }
print("filteredAnimals:", filteredAnimals)
filteredAnimals: [Dog, Cat, Otter, Deer, Rabbit]
Likely not the set you expected!
However this works fine this way if we don't type animalArray as an NSMutableArray:
let animalArray = ["Dog","Cat","Otter","Deer","Rabbit"]
let filteredAnimals = animalArray.filter { $0.rangeOfString("er") != nil }
print("filteredAnimals:", filteredAnimals)
filteredAnimals: [Otter, Deer]
However I'd recommend using $0.contains() instead of $0.rangeOfString() != nil because it functions in both circumstances and slightly enhances the readability of the code:
let animalArray: NSMutableArray = ["Dog","Cat","Otter","Deer","Rabbit"]
let filteredAnimals = animalArray.filter { $0.contains("er") }
print("filteredAnimals:", filteredAnimals)
filteredAnimals: [Otter, Deer]

How do I get the key at a specific index from a Dictionary in Swift?

I have a Dictionary in Swift and I would like to get a key at a specific index.
var myDict : Dictionary<String,MyClass> = Dictionary<String,MyClass>()
I know that I can iterate over the keys and log them
for key in myDict.keys{
NSLog("key = \(key)")
}
However, strangely enough, something like this is not possible
var key : String = myDict.keys[0]
Why ?
That's because keys returns LazyMapCollection<[Key : Value], Key>, which can't be subscripted with an Int. One way to handle this is to advance the dictionary's startIndex by the integer that you wanted to subscript by, for example:
let intIndex = 1 // where intIndex < myDictionary.count
let index = myDictionary.index(myDictionary.startIndex, offsetBy: intIndex)
myDictionary.keys[index]
Another possible solution would be to initialize an array with keys as input, then you can use integer subscripts on the result:
let firstKey = Array(myDictionary.keys)[0] // or .first
Remember, dictionaries are inherently unordered, so don't expect the key at a given index to always be the same.
Swift 3 : Array() can be useful to do this .
Get Key :
let index = 5 // Int Value
Array(myDict)[index].key
Get Value :
Array(myDict)[index].value
Here is a small extension for accessing keys and values in dictionary by index:
extension Dictionary {
subscript(i: Int) -> (key: Key, value: Value) {
return self[index(startIndex, offsetBy: i)]
}
}
You can iterate over a dictionary and grab an index with for-in and enumerate (like others have said, there is no guarantee it will come out ordered like below)
let dict = ["c": 123, "d": 045, "a": 456]
for (index, entry) in enumerate(dict) {
println(index) // 0 1 2
println(entry) // (d, 45) (c, 123) (a, 456)
}
If you want to sort first..
var sortedKeysArray = sorted(dict) { $0.0 < $1.0 }
println(sortedKeysArray) // [(a, 456), (c, 123), (d, 45)]
var sortedValuesArray = sorted(dict) { $0.1 < $1.1 }
println(sortedValuesArray) // [(d, 45), (c, 123), (a, 456)]
then iterate.
for (index, entry) in enumerate(sortedKeysArray) {
println(index) // 0 1 2
println(entry.0) // a c d
println(entry.1) // 456 123 45
}
If you want to create an ordered dictionary, you should look into Generics.
From https://developer.apple.com/library/prerelease/ios/documentation/swift/conceptual/swift_programming_language/CollectionTypes.html:
If you need to use a dictionary’s keys or values with an API that takes an Array instance, initialize a new array with the keys or values property:
let airportCodes = [String](airports.keys) // airportCodes is ["TYO", "LHR"]
let airportNames = [String](airports.values) // airportNames is ["Tokyo", "London Heathrow"]
SWIFT 3. Example for the first element
let wordByLanguage = ["English": 5, "Spanish": 4, "Polish": 3, "Arabic": 2]
if let firstLang = wordByLanguage.first?.key {
print(firstLang) // English
}
In Swift 3 try to use this code to get Key-Value Pair (tuple) at given index:
extension Dictionary {
subscript(i:Int) -> (key:Key,value:Value) {
get {
return self[index(startIndex, offsetBy: i)];
}
}
}
SWIFT 4
Slightly off-topic: But here is if you have an
Array of Dictionaries i.e: [ [String : String] ]
var array_has_dictionary = [ // Start of array
// Dictionary 1
[
"name" : "xxxx",
"age" : "xxxx",
"last_name":"xxx"
],
// Dictionary 2
[
"name" : "yyy",
"age" : "yyy",
"last_name":"yyy"
],
] // end of array
cell.textLabel?.text = Array(array_has_dictionary[1])[1].key
// Output: age -> yyy
Here is an example, using Swift 1.2
var person = ["name":"Sean", "gender":"male"]
person.keys.array[1] // "gender", get a dictionary key at specific index
person.values.array[1] // "male", get a dictionary value at specific index
I was looking for something like a LinkedHashMap in Java. Neither Swift nor Objective-C have one if I'm not mistaken.
My initial thought was to wrap my dictionary in an Array. [[String: UIImage]] but then I realized that grabbing the key from the dictionary was wacky with Array(dict)[index].key so I went with Tuples. Now my array looks like [(String, UIImage)] so I can retrieve it by tuple.0. No more converting it to an Array. Just my 2 cents.

Resources