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] }
Related
I have swift dictionary [String: Any] which I store in UserDefauls as an array [[String: Any]]
what I want to do is replace key: value with another one, e.g. "id": "x:coredataid"with"id": "server id"
I need to loop through array first and then through all key values. Is there any elegant solution for this purposes?
If not how then simple iterate through all key values and all nested levels in dictionary?
I have this code: for (key, value) in params
but it's only for top level keys.
Let me explain more in details. As you see I have phases key which an array. Also each phase contains day key which also an array.
So I don't care actually about key naming, phases it or days whether, what I want is to iterate all of key, values from provided [String: Any] dictionary and check if key contains a value which equal provided string.
As you see currently workoutId equals: <x-coredata://C3C82F5A-8709-4EDC-8AE5-C23C65F220D5/WorkoutEntity/t072831FB-8F5C-4589-85CB-6D084671C097815> I underscore it with red line.
So I want to loop a dictionary to catch this key workoutId and check if this equal <x-coredata://C3C82F5A-8709-4EDC-8AE5-C23C65F220D5/WorkoutEntity/t072831FB-8F5C-4589-85CB-6D084671C097815>
One more time I don't care about workoutId name, key can be actually named as exerciseId or id never mind. I just want to find a value <x-coredata://C3C82F5A-8709-4EDC-8AE5-C23C65F220D5/WorkoutEntity/t072831FB-8F5C-4589-85CB-6D084671C097815> in my entire dictionary and if there are lot of them replace all of them.
The reason why I need it is connected to identifier I store localy which are equals to CoreData identifiers as you my noticed. But when I modified my CoreData records with new identifiers returned from server I want to replace my UserDefaults off-line requests store with new ids.
I've added also modification to this code:
func update(_ dict: [String: Any], set serverId: Any, for localId: String) -> [String: Any] {
var newDict = dict
for (k, v) in newDict {
if let mobileLocalId = v as? String {
if mobileLocalId == localId {
newDict[k] = serverId
} else { newDict[k] = v }
} else if let subDict = v as? [String: Any] {
newDict[k] = update(subDict, set: serverId, for: localId)
} else if let dictionaries = v as? [[String: Any]] {
for dictionary in dictionaries {
newDict[k] = update(dictionary, set: serverId, for: localId)
}
}
}
return newDict
}
but it somehow drop days for me and newDict now looks like this:
(lldb) po newDict
▿ 2 elements
▿ 0 : 2 elements
- key : "position"
- value : 0
▿ 1 : 2 elements
- key : "workoutId"
- value : "5d51723b3faceb53f9d2d5ed"
where actully I susscefully changed identifiers, but now all other key pairs from above example are missed.
Here is a solution with a recursive function that replaces all values for a given key.
func update(_ dict: [String: Any], set value: Any, for key: String) -> [String: Any] {
var newDict = dict
for (k, v) in newDict {
if k == key {
newDict[k] = value
} else if let subDict = v as? [String: Any] {
newDict[k] = update(subDict, set: value, for: key)
} else if let subArray = v as? [[String: Any]] {
var newArray = [[String: Any]]()
for item in subArray {
newArray.append(update(item, set: value, for: key))
}
newDict[k] = newArray
}
}
return newDict
}
Note that it doesn't check what type the existing value is but directly replaces it with the new value. Also the code assumes the only types of nested arrays are arrays of dictionaries.
For the array this function can be used with map
let out = data.map { update($0, set: "newValue", for: "id")}
This recursive function will iterate through all key values:
func iterateThroughAllKeyValues<Key: Hashable, Value>(of dictionary: Dictionary<Key, Value>, execute execution: ((Key, Value))->()) {
for element in dictionary {
if let dictionary = element.value as? [Key: Value] {
iterateThroughAllKeyValues(of: dictionary, execute: execution)
} else {
execution(element)
}
}
}
Also you can achieve calling execution on the main node of any nested dictionary with a little bit of change.
And this is the extension mode:
extension Dictionary {
func iterateThroughAllKeyValues(execute execution: ((Key, Value))->()) {
for element in self {
if let dictionary = element.value as? [Key: Value] {
dictionary.iterateThroughAllKeyValues(execute: execution)
} else {
execution(element)
}
}
}
}
Note: Careful about the order
Usage Example:
let dictionary: [String: Any] = [
"id0": "value0",
"nested": ["id1": "value1"],
"nestedNested": ["id2": "value2",
"nested": ["id3": "value3"]]
]
dictionary.iterateThroughAllKeyValues { (key, value) in
print("key:", key, "Value:", value)
}
Output:
key: id0 Value: value0
key: id1 Value: value1
key: id3 Value: value3
key: id2 Value: value2
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.
I have a dictionary, like this:
{"Matt":"Apple", "Calvin":"Nut", "Susie":"Pear"}
I want to check the keys of the dictionary with a string, to see if they contain that string-- and if they do, I want to add their respective value to an array
So, if the input was "a"
the return would be ["Apple", "Nut"] because "Matt" and "Calvin" matched the "a"
Basically looking for some Swift tips,
otherwise I was going to implement it like this:
Grab all keys, put into an array
Filter keys for string value, this is the keyArray
Enumerate over keyArray, and get all their values from the dictionary
Boom have an array of values
You can filter the dictionary, using contains() on each key,
and then extract the corresponding values:
let dict = [ "Matt":"Apple", "Calvin":"Nut", "Susie":"Pear" ]
let input = "a"
let values = Array(dict.filter { $0.key.contains(input) }.values)
print(values) // ["Apple", "Nut"]
Or with flatMap() (renamed to compactMap() in Swift 4.1):
let values = dict.flatMap { $0.key.contains(input) ? $0.value : nil }
print(values) // ["Apple", "Nut"]
Here each dictionary entry is mapped to the value if the key contains
the given string, and to nil otherwise. flatMap() then returns an
array with the non-nil values.
Simply using a filter over the dictionary:
let dict: [String: String] = ["Matt": "Apple", "Calvin": "Nut", "Susie": "Pear"]
func findValuesMatching(search: String, in dict: [String: String]) -> [String] {
return dict
.filter { (key, value) in
return key.range(of: search) != nil
}.map { (key, value) in
return value
}
}
print(findValuesMatching(search: "a", in: dict))
Do it like this:
let dict = ["Matt":"Apple", "Calvin":"Nut", "Susie":"Pear"]
let containsA = dict.filter({ $0.key.lowercased().contains("a") }).map({ $0.value })
Output:
["Apple", "Nut"]
let json: [AnyObject] = {
"response": "get_nearby_deals",
"userID": "12345",
"demo":[{"deal_code":"iD1612061"}]
}
How to declare Dictionary in Swift? I'm new in Swift. Totally stuck.
You have declared Array using [AnyObject], just change it to [String: Any] and replace curly braces {} with square brackets [].
let json: [String: Any] = [
"response": "get_nearby_deals",
"userID": "12345",
"demo":[["deal_code":"iD1612061"]]
]
And you can retrieve value from Dictionary using subscript like this.
let userID = json["userID"] as! String
//Above will crash if `userID` key is not exist or value is not string, so you can use optional wrapping with it too.
if let userID = json["userID"] as? String {
print(userID)
}
//`demo` value is an array of [String:String] dictionary(s)
let demo = json["demo"] as! [[String:String]]
//Same here as the previous `userID`, it will crash if 'demo' key is not exist, so batter if you optionally wrapped it:
if let demo = json["demo"] as? [[String:String]] {
print(demo)
}
I've initialized a dictionary of type [NSObject: AnyObject] so I can save it into NSUserDefaults.
Here's what it looks like:
var allMetadata: [NSObject: AnyObject] = [
String: [String: String]
// Example: "project30": ["deliverablepath": "hello"]
]
I give deliverablepath a value from the very beginning, and later on I want to update it. I've tried this:
allMetadata[arrayOfProjectIDs[index]]!["deliverablepath"]! = "goodbye"
But I get the error
Operand of postfix '!' should have optional type; type is '(NSObject,
AnyObject)'
I know about updateValue(), but it seems to overwrite adjacent keys in the first nested layer, so it's not working for me.
Any ideas?
Use question optional to avoid "let pyramid"
var allMetadata: [String: [String: String]] = ["a": ["b": "c"]]
allMetadata["a"]?["b"] = "z" // ok!
allMetadata["q"]?["b"] = "d" // nil
UPD:
If you want to cast directly, you should try this:
var allMetadata: [NSObject: AnyObject] = ["a": ["b": "c"]]
if var dict = allMetadata["a"] as? [String: String] {
dict["b"] = "z"
// for dict update, because it's value typed
allMetadata["a"] = dict
}
Mention, that I've written "var", not "let" in condition.
To do this in a safe way, it is best to do this in an if let pyramid as follows:
if let projectId = arrayOfProjectIDs[index] {
if var project = allMetadata[projectId] as? [String:String] {
project["deliverablePath"] = "Goodbye"
}
}
That is not too bad actually.
I want to give an alternative answer here.
I understand the original question is about how to deal with nested arrays and dictionaries, but I think it is worth mentioning that this kind of data model may be better implemented with a more formal API.
For example, how about this:
class Project {
var id: String
var deliverablePath: String
... etc ...
}
class ProjectRepository {
func getProjectWithId(id: String) -> Project? {
...
}
}
Then you can use high level code like:
if let project = repository.getProjectWithId("") {
project.deliverablePath = "Goodbye"
}
Underneath you can still implement this with dictionaries and arrays of course.