I've looked at other subscript issues here and I don't think they match my problem. I have a dictionary of dictionaries - Dictionary[String:Dictionary[String:String]]
In an extension I want to loop through all the values (Dictionary[String:String] and retrieve one of the values.
So I wrote this:
for dictNEO in Array(self.values) {
print(dictNEO)
print(type(of: dictNEO))
print(dictNEO["approachDate"])
}
and am getting this error on the last print line: Value of type 'Value' has no subscripts
Here's the first two print lines:
["nominalDist": "\"13.58 ", "approachDate": "\"2020-Feb-01 08:18 ± < 00:01\"", "minimumDist": "\"13.58 ", "diameter": "\"92 m - 210 m\"", "name": "\"(2017 AE5)\""]
Dictionary<String, String>
So I am confused as to why it is telling me it has no subscripts when it sees the type of as a Dictionary.
You have written this as an extension to Dictionary if I understand you correctly and that means that self is generic and defined as Dictionary<Key, Value> and not to you specific type so in your for loop you are looping over an array of [Value].
So you need to typecast Value before accessing it as a dictionary
if let dictionary = dictNEO as? [String: String] {
print(dictNEO["approachDate"])
}
but since it makes little sense to have an extension to Dictionary where you access a specific key it would be better to write it as a function. Since the dictionary is well defined now there is no issue with the last print
func printValuesForSubKey(_ key: String, _ dict: [String: [String: String]]) {
for (dictNEO) in dict.values {
print(dictNEO)
print(type(of: dictNEO))
print(dictNEO[key])
}
}
Note, I don't have an explanation why type(of:) recognises it as [String: String]
The code snippet doesn't work because values property is a collection of collections and with Array(values) you create a collection of collection of collections. In short, instead going down the code goes up and creates new collection level.
Solution with a Higher order function map:
self.values.map { print(type(of: $0)); $0["approachDate"] }
Solution with For-In Loop
for dictNEO in self.values {
print(dictNEO)
print(type(of: dictNEO))
print(dictNEO["approachDate"])
}
Related
I have an issue where I am able to sort the dictionary however the result is weird.
class MyModel {
var dict = [Date: Int]()
...
}
let sortDict = dict.sorted { (first, second) -> Bool in
return first.key > second.key
}
dictDatePlacesCount = sortDict[0]
The issue is that I get a syntax error
Cannot assign value of type 'Dictionary<Date, Int>.Element' (aka '(key: Date, value: Int)') to type '[Date : Int]'
The question is that I cannot create a custom Dictionary with this rule of sorting in descending order with the Date?
I did explore and found OrderedDictionary but it seems like an external package?
As jnpdx says, the Dictionary method sorted() returns an array of tuples of type (Key, Value) (So (Date, Int) in your case.)
The Swift standard library doesn't have an ordered dictionary. Again, as mentioned by jnpx, there are frameworks/libraries like the official Swift Collections that offer ordered dictionaries. You might want to use one of those.
Alternatively, you could sort the keys from your dictionary and use those to index into your contents.
I am attempting to get the values from an NSSet in core data and append those values to an array of type String.
func addStepsToArray() {
if let steps = entity.steps {
for i in steps {
recipeStep.append(String(i))
}
}
}
entity.steps is the list of steps tied to a core data entity. This is an NSSet. I am trying to copy those values to an array of type String.
#State var recipeStep: [String]
When trying to do this in my for in loop, I receive the following error: No exact matches in call to initializer
If I remove the conversion of "I" to String, I receive the following error:
Cannot convert value of type NSSet.Element (aka Any) to expected argument type String
Any idea on how to get this to work?
NSSet is defined in Objective C, which didn't have generics. It's an untyped collection, so you don't statically know anything about its elements.
As you've noticed, your i variable isn't a String, it's an Any.
You're confusing type coercion ("casting") with type conversion. If i were a Double, you could call String(i) to invoke an initializer which takes a double, and processes into a String.
You tried something similar by calling String(i), where you're making the Swift compiler find an initializer on String with the signitiure init(_: Any).
There is no such initializer. And besides, that's not what you want. You don't want to create a new String from a different kind of value. You already have a string, it's just "hidden" behind an Any reference.
What you're looking for is to do a down-cast, from Any to String:
func addStepsToArray() {
if let steps = entity.steps {
for i in steps {
guard let step = i as? String else {
fatalError("Decide what to do if the value isn't a String.")
}
recipeStep.append(i as String)
}
}
}
I'll warn you though, there are several issues/blemishes with this code:
You're using a for loop to do what is ultimately just a mapping operation
Your computation doesn't return its ouput, and instead indirectly achieves its goal through a side-effect on recipeStep
Your computation doesn't take a its input as a parameter, and instead indirectly achieves its goal through a side-effect on entity
i is conventionally expected to be an integer index of a for loop iterating over a sequence of numbers. Here it's an Any (a String at runtime)
Here's what I would suggest instead:
func getRecipeSteps(from entity: MyEntityType) -> [String] {
guard let steps = entity.steps else { return [] }
return steps.map { step in
guard let stringStep = step as? String else {
fatalError("Decide what to do if the value isn't a String.")
}
return step
}
}
Then in the rest of your code (and your tests), you can write self.recipeSteps = getRecipeSteps(from: myEntity). Elegant!
If you're certain that these entity.steps values can only ever be strings, then you can boil this down to a single map with a force-cast:
func getRecipeSteps(from entity: MyEntityType) -> [String] {
entity.steps?.map { $0 as! String } ?? []
}
Just convert directly:
let set = Set(["1", "2", "3"])
let array = Array(set)
DDLog(set)//Set<String>)
DDLog(array)//[String]
I have this Dictionary, which I am getting from a web service:
let json = try JSONSerialization.jsonObject(with: data!, options:.allowFragments) as! Dictionary<String, AnyObject>
and now I am trying to sort them alphabetically like so:
self.appDelegate.communityArray = json.sorted(by: {$0.0 < $1.0})
But I get this error:
Cannot assign value of type '[(key: String, value: AnyObject)]' to
type 'Dictionary?'
What am I doing wrong?
This is how I am defining communityArray:
var communityArray: Dictionary<String, AnyObject>?
As mentioned in the comments a dictionary – a collection type containing key-value pairs – is unordered by definition, it cannot be sorted.
The sorted function of collection applied to a dictionary treats the dictionary for example
["foo" : 1, "bar" : false]
as an array of tuples
[(key : "foo", value : 1), (key : "bar", value : false)]
and sorts them by key (.0) or value (.1) (I suspect sorting by value Any will raise a compiler error)
The error occurs because you cannot assign an array of tuples to a variable declared as dictionary. That's almost the literal error message.
As a compromise I recommend to map the dictionary to a custom struct (similar to a tuple but better to handle)
struct Community {
let key : String
let value : Any
}
Your variable name already implies that you want a real array
var communityArray = [Community]()
let json = try JSONSerialization.jsonObject(with: data!) as! Dictionary<String, Any>
communityArray = json.map { Community(key: $0.0, value: $0.1 }.sorted { $0.key < $1.key }
sorted(by:) Returns the elements of the sequence, sorted using the given predicate as the comparison between elements, so you can not use as a dictionary.
For more info about this function you may read sorted(by:) documentation
In this playground example I'm hoping to find an extension that will remove any nils I put into the creation of a dictionary.
var someValue: String?
if false {
someValue = "test"
}
var dict = ["key": "value",
"key2": someValue]
print("\(dict)")
dict["key3"] = nil
print("\(dict)")
In the above code the current log is
[AnyHashable("key2"): nil, AnyHashable("key"): Optional("value")]
[AnyHashable("key2"): nil, AnyHashable("key"): Optional("value")]
key3 is never added because setting = nil tells it to be removed. I would like to add that functionality to the initial creation of the dictionary but have yet to find a solution that works.
A working solution would result in the following print out
[AnyHashable("key"): Optional("value")]
[AnyHashable("key"): Optional("value")]
This is not solvable in Swift. The correct way to write it is:
var dict = ["key": "value"]
if false {
dict["key2"] = "test"
}
Swift doesn't provide the kind of syntax you're describing, and trying to force it to is going to break the type and create buggy situations. Do not try to create [AnyHashable: Any?]. That is a completely broken type that's going to burn you (Any? is completely broken as a type because Optional is Any, and anything can implicitly become Optional, so it becomes a bizarre recursive rabbit hole). [AnyHashable: Any] is acceptable if you must bridge to NSDictionary, but in general it should be strongly avoided and limited to just where you need it.
Note that this was much even more broken in ObjC (you could write this kind of stuff, but then it'd crash or truncate your dictionary, or some other weird bug), so at least we're making some progress.
In a lot of cases when I see people run into this problem, it's because they've overused optionals in the first place. In your user.name example, why is name optional in the first place? Is there any difference between nil and ""? If there isn't (and there usually isn't), then just make name non-optional (nonnullable in ObjC) and default it to empty and lots of problems go away. Having two versions of the same value (i.e. nil and "" have the same meaning) indicates a type problem, not a syntax problem.
If you want to simplify the syntax just a little bit with a quick extension that works on key/value? pairs like this:
extension Dictionary {
init(keyOptionalPairs: [(Key, Value?)]) {
var d: [Key: Value] = [:]
for (key, value) in keyOptionalPairs {
d[key] = value
}
self = d
}
}
let keyValues: [(String, String?)] = [
("key", "value"),
("key2", nil)
]
let dict = Dictionary(keyOptionalPairs: keyValues)
But notice that the Dictionary is [String: String], not [String: String?]. That's on purpose.
Compared to your syntax, it just adds a set of parens. But compare to the non-fancy version, which isn't beautiful, but is very straightforward.
let dict: [String: String] = {
var d: [String: String] = [:]
d["key"] = "value"
d["key2"] = nil
return d
}()
In Swift The Programming Language Book, I quote:
You can use subscript syntax to remove a key-value pair from a dictionary by assigning a value of nil for that key
They said that assigning a value to nil while remove the pair, but it is not mentioned while initializing the dictionary, so I think it is not valid.
If you really need to do that, I suggest to do some logic after initializing the dictionary to do that for you, like that:
for (key, value) in dict {
if value == nil {
dict[key] = nil
}
}
I'm storying an array of JavaScript key/value objects in a column of type Array on Parse like this:
[{"1432747073241":1.1},{"1432142558000":3.7}]
When I retrieve that column in Swift, I can see the data, but I'm unsure what data type to cast it as:
if let data = dashboardObject[graphColumn] as? [AnyObject]{
for pair in data{
println(pair)
}
}
That print yields this in the console (for the first pair):
{
1432747073241 = "1.1";
}
I can't seem to cast its contents as a Dictionary [Int:Double] and I'm guessing that means this is a string.
How do I parse this data in Swift? Thanks.
The Dictionary you should parse it to is [String: AnyObject]. It seems as if the keys of this dictionary are timestamps which you probably don't know. You could iterate through the dictionary like this:
for (key, value) in pair {
// do what you want in here with the value and/or the key
}