Check if Dictionary Key contains String - swift

Say I have the following dictionary ["Lionel Messi":"170cm"]
Would it be possible to find that key-value pair if i only knew part of key string. In other words, would it be possible to find the above mentioned key-value pair if i only had the string "Lione".

func findPartOfString(partOfKey: String, myDict: Dictionary) -> String {
for (key, value) in myDict {
if key.containsString(partOfKey) {
return value
}
}
return null
}
EDIT:
Here's a new shorter way with Swift2:
func findPartOfString(partOfKey: String, myDict: Dictionary) -> String {
for (key, value) in myDict where key.containsString(partOfString) {
return value
}
return null

let filter = "Lionel"
let dict = ["Lionel Messi" : "170cm", "Me" : "Taller"]
let result = dict.keys.filter { $0.containsString(filter) }
if let first = result.first {
print("match found: (\(first) => \(dict[first]!))")
}
Outputs
match found: (Lionel Messi => 170cm)

Related

Most common occurrence of a name in a string compile error

I am writing a function that returns a dictionary that contains the name with most occurrences as the key and the number of occurrences as the value, however, I get an error - see the code below:
let names = ["Adam", "Bob", "Charlie", "Dylan", "Edward"]
func getMostCommonName(array: [String]) -> [String: Int] {
var namesDictionary: [String: Int] = [:] // holds all the names and their occurrences
var mostCommonNames: [String: Int] = [:] // will hold the most common name(s) and their occurrences
for name in array {
if let count = namesDictionary[name] {
namesDictionary[name] = count + 1
}
else {
namesDictionary[name] = 1
}
}
let highestOccurence = namesDictionary.values.max()
for name in namesDictionary {
if namesDictionary[name] == highestOccurence {
mostCommonNames[name] = highestOccurence // throws an error
}
}
return mostCommonNames
}
getMostCommonName(array: names)
The error is Cannot subscript a value of type '[String : Int]' with an index of type '(key: String, value: Int)'. I really don't understand why it would throw this error. Any takers?
Because name is type of (key: String, value: Int),it is tuple type,you can access key and value like this
for element in namesDictionary {
if element.value == highestOccurence {
mostCommonNames[element.key] = highestOccurence
}
}
Also, I recommend to write for in like this
for (key,value) in namesDictionary {
if value == highestOccurence {
mostCommonNames[key] = highestOccurence
}
}
More about tuple type: document
You can also try this:
for name in namesDictionary {
if namesDictionary[name.key] == highestOccurence {
mostCommonNames[name.key] = highestOccurence
}
}

Recursively change some keys in a Dictionary in Swift

I have a dictionary
var dictionary: Any = ["det" : ["val" : "some","result" : ["key1" : "val1","key2" : "val2"],"key3" :["val1", "val2"]]]
and a mapping function below
func getMappedKey(_ key: String) -> String? {
var mapping: Dictionary = [
"key1" : "key3",
"key2" : "key4",
"det" : "data"
]
return mapping[key]
}
Now I want to change some keys in the same dictionary using the mapping function above. So after the change, the dictionary should look like
["data" : ["val" : "some","result" : ["key3" : "val1","key4" : "val2"],"key3" :["val1", "val2"]]]
So for that I wrote a function below
func translatePayload(_ payload: inout Any) {
if let _ = payload as? String {
return
} else if var payload = payload as? Dictionary<String, Any> {
for (key, value) in payload {
if let newKey = getMappedKey(key) {
if let _ = payload.removeValue(forKey: key) {
payload[newKey] = value
}
}
var nextPayload = value
translatePayload(&nextPayload)
}
} else if let payload = payload as? Array<Any> {
for value in payload {
var nextPayload = value
translatePayload(&nextPayload)
}
}
}
and when I call the function
translatePayload(&dictionary)
print(dictionary)
it does not change the keys in the same dictionary. Can someone please point out what is wrong with this code. Thanks in advance
Your code is perfectly fine, you just updated the local variable instead of the parametric one because you used the same name. Just change the local variable payload to dictionary and array or anything else you like.
Here is the final code:
func translatePayload(_ payload: inout Any) {
if let _ = payload as? String {
return
} else if var dictionary = payload as? Dictionary<String, Any> { // Here dictionary instead of payload
for (key, value) in dictionary {
var nextPayload = value
translatePayload(&nextPayload)
if let newKey = getMappedKey(key) {
if let _ = dictionary.removeValue(forKey: key) {
dictionary[newKey] = nextPayload
}
} else {
dictionary[key] = nextPayload
}
}
payload = dictionary
} else if let array = payload as? Array<Any> { // Here array instead of payload
var updatedArray = array
for (index, value) in array.enumerated() {
var nextPayload = value
translatePayload(&nextPayload)
updatedArray[index] = nextPayload
}
payload = updatedArray // Assign the new changes
}
}
translatePayload(&dictionary)
print(dictionary)
Not really a direct answer to the question "what's wrong", but I'd go with something like:
let dictionary = ["det" : ["val" : "some","result" : ["key1" : "val1", "key2" : "val2"],"key3" :["val1", "val2"]]]
func getMapped(key: String) -> String {
var mapping: Dictionary = [
"key1" : "key3",
"key2" : "key4",
"det" : "data"
]
return mapping[key] ?? key
}
func translate(payload:Any, map:(String)->String) -> Any {
switch payload {
case let value as String:
return value
case let value as [String:Any]:
return value.reduce(into:[String:Any]()) {
$0[map($1.0)] = translate(payload: $1.1, map:map)
}
case let value as [Any]:
return value.map { translate(payload: $0, map:map) }
default:
fatalError("Unknown data type")
}
}
let output = translate(payload: dictionary, map:getMapped(key:))
To really take advantage of the functional spirit of Swift.

calling filter on complex dictionary (associative array) in swift

I have this array:
class Filter {
var key = ""
var value = ""
init(key: String, value: String) {
self.key = key
self.value = value
}
}
let arry = [
"a":[Filter(key:"city",value:"aachen"),Filter(key:"city",value:"augsburg")],
"b":[Filter(key:"city",value:"bremen"),Filter(key:"city",value:"berlin")]
]
and I want to look for augsburg and remove it from the dictionary with the filter function so the output looks like this:
let arry = [
"a":[Filter(key:"city",value:"aachen")],
"b":[Filter(key:"city",value:"bremen"),Filter(key:"city",value:"berlin")]
]
I tried it with many filter and map constelations but I always get this structure as result:
let arry = [
["a":[Filter(key:"city",value:"aachen")]],
["b":[Filter(key:"city",value:"bremen"),Filter(key:"city",value:"berlin")]]
]
for example with this filter:
arry.map({ key,values in
return [key:values.filter{$0.value != "augsburg"}]
})
What is the problem here? How can I filter and map over more complex objects?
Maybe one thing you should know is that map method of Dictionary returns Array, not Dictionary.
public func map<T>(_ transform: (Key, Value) throws -> T) rethrows -> [T]
So, if you want the filtered result as Dictionary, you may need to use reduce:
class Filter: CustomStringConvertible {
var key = ""
var value = ""
init(key: String, value: String) {
self.key = key
self.value = value
}
//For debugging
var description: String {
return "<Filter: key=\(key), value=\(value)>"
}
}
let dict = [
"a":[Filter(key:"city",value:"aachen"),Filter(key:"city",value:"augsburg")],
"b":[Filter(key:"city",value:"bremen"),Filter(key:"city",value:"berlin")]
]
let filteredDict = dict.reduce([:]) {tempDict, nextPair in
var mutableDict = tempDict
mutableDict[nextPair.key] = nextPair.value.filter {$0.value != "augsburg"}
return mutableDict
}
(Generally, Swift Dictionary is a hash-table based implementation of associative array, but you should better avoid naming arry for Dictionary variables. Such naming is so confusing.)
Or else simply use for-in loop:
var resultDict: [String: [Filter]] = [:]
for (key, value) in dict {
resultDict[key] = value.filter {$0.value != "augsburg"}
}
print(resultDict) //->["b": [<Filter: key=city, value=bremen>, <Filter: key=city, value=berlin>], "a": [<Filter: key=city, value=aachen>]]

Using an enum's default value as a dictionary key without explicitly casting to String

If I declare an enum like this:
enum Keys {
case key_one
case key_two
}
I can print it and it will be automatically converted to a String:
print(Keys.key_one) // prints "key_one"
If I then make a dictionary that maps Strings to whatever (but let's choose Strings again for simplicity), I think that it should be able to add a key by using Keys.key_one as the key, right? Wrong.
var myDict = [String : String]()
/// error: cannot subscript a value of type '[String : String]' with an index
/// of type 'Keys'
myDict[Keys.key_one] = "firstKey"
I can do it if I explicitly convert Keys.key_one to a String like this though:
myDict[String(Keys.key_one)] = "firstKey"
print(myDict) // ["key_one": "firstKey"]
So I want to do this without having to wrap my enum with String() every time.
I've tried a few things by doing an extension off of Dictionary using the where keyword with the Key and trying to implement new subscript functions, but I can't get it to work. What's the trick?
Update and improvement for Swift 4.2
extension Dictionary {
subscript(key: APIKeys) -> Value? {
get {
guard let key = key.stringValue as? Key else { return nil }
return self[key]
}
set(value) {
guard let key = key.stringValue as? Key else { return }
guard let value = value else { self.removeValue(forKey: key); return }
self.updateValue(value, forKey: key)
}
}
}
protocol APIKeys {}
extension APIKeys {
var stringValue: String {
return String(describing: self)
}
}
enum Keys: APIKeys {
case key_one
case key_two
}
var myStringDict = [AnyHashable : Any]()
var model1StringDict = [String : Any]()
var model2StringDict = [String : String]()
myStringDict.updateValue("firstValue", forKey: Keys.key_one.stringValue) // [key_one: firstValue]
myStringDict[Keys.key_two] = "secondValue" // [key_two: secondValue, key_one: firstValue]
myStringDict[Keys.key_one] = nil // [key_two: secondValue]
myStringDict.removeValue(forKey: Keys.key_two.stringValue) // []
model1StringDict.updateValue("firstValue", forKey: Model1Keys.model_1_key_one.stringValue) // [model_1_key_one: firstValue]
model1StringDict[Model1Keys.model_1_key_two] = "secondValue" // [model_1_key_two: secondValue, model_1_key_one: firstValue]
model1StringDict[Model1Keys.model_1_key_one] = nil // [model_1_key_two: secondValue]
model2StringDict.updateValue("firstValue", forKey: Model2Keys.model_2_key_one.stringValue) // [model_2_key_one: firstValue]
model2StringDict[Model2Keys.model_2_key_two] = "secondValue" // [model_2_key_two: secondValue, model_2_key_one: firstValue]
model2StringDict[Model2Keys.model_2_key_one] = nil // [model_2_key_two: secondValue]
I specifically changed the types of the 3 dictionaries to show the common ways of typing a dictionary ([AnyHashable : Any], [String : Any], and [String : String]), and showed that this works with each of the types.
It's important to note that if you use updateValue instead of the assignment operator when the key of your dictionary is AnyHashable, then you need to specify the String value of the key with .stringValue. Otherwise, the exact type of the key being stored will not explicitly be a String, and it'll get messed up later if you try to, say, remove a value under your key via assigning nil to that key. For a dictionary where the key is specifically typed to be String, then the updateValue function will have a compile time error saying that the key needs to be a String, so you can't mess it up that way.
Swift 2.3
I figured out the extension solution that I wanted.
extension Dictionary {
subscript(key: Keys) -> Value? {
get {
return self[String(key) as! Key]
}
set(value) {
guard
let value = value else {
self.removeValueForKey(String(key) as! Key)
return
}
self.updateValue(value, forKey: String(key) as! Key)
}
}
}
enum Keys {
case key_one
case key_two
}
var myStringDict = [String : String]()
/// Adding the first key value through the String() way on purpose
myStringDict.updateValue("firstValue", forKey: String(Keys.key_one))
// myStringDict: ["key_one": "firstValue"]
// myStringDict[Keys.key_one]!: firstValue
myStringDict[Keys.key_two] = "secondValue"
// myStringDict: ["key_one": "firstValue", "key_two": "secondValue"]
myStringDict[Keys.key_one] = nil
// myStringDict: ["key_two": "secondValue"]
Notice that the declared dictionary key type is String, but I'm able to just use Keys.key_one and the subscript in the dictionary extension takes care of the rest.
I can probably put some better guarding around the as! conversion to Key, but I'm not sure it's needed, as I know that my enum can always be converted to a valid Key by the String() cast.
Improvement to answer
Even better, since I'm using this for API Keys, I made a blank protocol called APIKeys and each model will implement their own Keys enum that conforms to the APIKeys protocol. And the dictionary's subscript is updated to take in APIKeys as the Key value.
extension Dictionary {
subscript(key: APIKeys) -> Value? {
get {
return self[String(key) as! Key]
}
set(value) {
guard
let value = value else {
self.removeValueForKey(String(key) as! Key)
return
}
self.updateValue(value, forKey: String(key) as! Key)
}
}
}
protocol APIKeys {}
enum Keys: APIKeys {
case key_one
case key_two
}
enum Model1Keys: APIKeys {
case model_1_key_one
case model_1_key_two
}
enum Model2Keys: APIKeys {
case model_2_key_one
case model_2_key_two
}
var myStringDict = [String : String]()
var model1StringDict = [String : String]()
var model2StringDict = [String : String]()
myStringDict.updateValue("firstValue", forKey: String(Keys.key_one)) // myStringDict: ["key_one": "firstValue"]
myStringDict[Keys.key_two] = "secondValue" // myStringDict: ["key_one": "firstValue", "key_two": "secondValue"]
myStringDict[Keys.key_one] = nil // myStringDict: ["key_two": "secondValue"]
model1StringDict.updateValue("firstValue", forKey: String(Model1Keys.model_1_key_one)) // model1StringDict: ["model_1_key_one": "firstValue"]
model1StringDict[Model1Keys.model_1_key_two] = "secondValue" // model1StringDict: ["model_1_key_one": "firstValue", "model_1_key_two": "secondValue"]
model1StringDict[Model1Keys.model_1_key_one] = nil // model1StringDict: ["model_1_key_two": "secondValue"]
model2StringDict.updateValue("firstValue", forKey: String(Model2Keys.model_2_key_one)) // model2StringDict: ["model_2_key_one": "firstValue"]
model2StringDict[Model2Keys.model_2_key_two] = "secondValue" // model2StringDict: ["model_2_key_one": "firstValue", "model_2_key_two": "secondValue"]
model2StringDict[Model2Keys.model_2_key_one] = nil // model2StringDict: ["model_2_key_two": "secondValue"]
Or you can do:
enum Keys: String {
case key_one
case key_two
}
And use it like this:
var myDict = [String : String]()
myDict[Keys.key_one.rawValue] = "firstKey"
Update
If you really want to make it work, you can do as below. But these is some risks by forcing the unwrapping.
extension Dictionary where Key: StringLiteralConvertible {
subscript (key: Keys) -> Value? {
get {
return self[key.rawValue as! Key]
}
set {
self[key.rawValue as! Key] = newValue
}
}
}
And then you use it like this:
myDict[Keys.key_one] = "firstKey"
myDict[Keys.key_one]
How important is it that your keys are Strings? If it's not important, I suggest doing something like:
enum Keys {
case key_one
case key_two
}
var myDict = [Keys : String]()
myDict[Keys.key_one] = "firstKey"
print(myDict) // [Keys.key_one: "firstKey"]

Case insensitive Dictionary in Swift

Given a Dictionary whose Key is of type String, is there a way to access the value in a case-insensitive manner? For example:
let dict = [
"name": "John",
"location": "Chicago"
]
Is there a way to call dict["NAME"], dict["nAmE"], etc. and stil get "John"?
A cleaner approach, swift 4:
extension Dictionary where Key == String {
subscript(caseInsensitive key: Key) -> Value? {
get {
if let k = keys.first(where: { $0.caseInsensitiveCompare(key) == .orderedSame }) {
return self[k]
}
return nil
}
set {
if let k = keys.first(where: { $0.caseInsensitiveCompare(key) == .orderedSame }) {
self[k] = newValue
} else {
self[key] = newValue
}
}
}
}
// Usage:
var dict = ["name": "John"]
dict[caseInsensitive: "NAME"] = "David" // overwrites "name" value
print(dict[caseInsensitive: "name"]!) // outputs "David"
Swift support multiple subscripting so you can take advantage of that to define a case-insensitve accessor:
extension Dictionary where Key : StringLiteralConvertible {
subscript(ci key : Key) -> Value? {
get {
let searchKey = String(key).lowercaseString
for k in self.keys {
let lowerK = String(k).lowercaseString
if searchKey == lowerK {
return self[k]
}
}
return nil
}
}
}
// Usage:
let dict = [
"name": "John",
"location": "Chicago",
]
print(dict[ci: "NAME"]) // John
print(dict[ci: "lOcAtIoN"]) // Chicago
This extension is limited to Dictionary whose Key is of type String (as lowercase is meaningless with other data types). However, Swift will complain about constraining a generic type to a struct. The protocol that is closest to String is StringLiteralConvertible.
Note that if you have 2 keys whose lowercase forms are identical, there's no guarantee which one you will get back:
let dict = [
"name": "John",
"NAME": "David",
]
print(dict[ci: "name"]) // no guarantee that you will get David or John.
The existing answers are fine, but the time complexity of lookups/insertions with those strategies deteriorates from O(1) to O(N) (where N is the number of objects in the dictionary).
To retain O(1) you may want to consider the following approach:
/// Wrapper around String which uses case-insensitive implementations for Hashable
public struct CaseInsensitiveString: Hashable, LosslessStringConvertible, ExpressibleByStringLiteral {
public typealias StringLiteralType = String
private let value: String
private let caseInsensitiveValue: String
public init(stringLiteral: String) {
self.value = stringLiteral
self.caseInsensitiveValue = stringLiteral.lowercased()
}
public init?(_ description: String) {
self.init(stringLiteral: description)
}
public var hashValue: Int {
return self.caseInsensitiveValue.hashValue
}
public static func == (lhs: CaseInsensitiveString, rhs: CaseInsensitiveString) -> Bool {
return lhs.caseInsensitiveValue == rhs.caseInsensitiveValue
}
public var description: String {
return value
}
}
var dict = [CaseInsensitiveString: String]()
dict["name"] = "John"
dict["NAME"] = "David" // overwrites "name" value
print(dict["name"]!) // outputs "David"
can use Collection's first(where:) to find first lowercased match from all keys mapped lowercased, then return the value from this result.
extension Dictionary where Key == String {
func valueForKeyInsensitive<T>(key: Key) -> T? {
let foundKey = self.keys.first { $0.compare(key, options: .caseInsensitive) == .orderedSame } ?? key
return self[foundKey] as? T
}
}
first(where:) is a much efficient way to filter or iterate over the large collection
reference:
https://developer.apple.com/documentation/swift/anybidirectionalcollection/2906322-first#
https://github.com/realm/SwiftLint/blob/master/Rules.md#first-where
This should do the job with O(1) while also not allowing to add the same string with different casing (e.g. if you first insert Def it is not replaced by DEF). It also works for Substring if necessary. Note, that this solution is more memory effective, but comes at the cost at recomputing the string transformation and hash on every lookup of a string. If you need to look-up the same value frequently it might be worth to have an implementation which caches the hashValue.
struct CaseInsensitiveString<T: StringProtocol>: Hashable, Equatable, CustomStringConvertible {
var string: T
init(_ string: T) {
self.string = string
}
var description: String { get {
return string.description
}}
var hashValue: Int { get {
string.lowercased().hashValue
} }
func hash(into hasher: inout Hasher) {
hasher.combine(hashValue)
}
static func == (lhs: Self, rhs: Self) -> Bool {
return lhs.string.compare(rhs.string, options: .caseInsensitive) == .orderedSame
}
}
typealias SubstringCI = CaseInsensitiveString<String>
var codeMap = [SubstringCI: Int]()
let test = "Abc Def Ghi"
let testsub = test[test.firstIndex(of: "D")!...test.lastIndex(of: "f")!]
codeMap[SubstringCI(String(testsub))] = 1
print(codeMap.keys, codeMap[SubstringCI("Def")]!, codeMap[SubstringCI("def")]!)
codeMap[SubstringCI("DEF")] = 1
print(codeMap.keys, codeMap[SubstringCI("Def")]!, codeMap[SubstringCI("def")]!)