Removing NSNull from Key Path Results with Partial Matches - ios

Given a data structure with mismatching objects:
1> import Foundation
2> let d: NSDictionary = ["test": [["name": "Dick", "age": 101], ["name": "Jane"]]]
valueForKeyPath: will return the values for the total number of sub-objects:
3> d.valueForKeyPath("test.name") as! NSArray
$R2: NSArray = "2 values" {
[0] = "Dick"
[1] = "Jane"
}
Even when the leaf key doesn't exist in all cases:
4> d.valueForKeyPath("test.age") as! NSArray
$R3: NSArray = "2 values" {
[0] = Int64(101)
[1] = {
NSObject = {
isa = NSNull
}
}
}
Is there some way to only get the existing ages, without an instances of NSNull?
#distinctUnionOfArrays and so on helps if there are multiple sub-objects without the leaf key, but you're still left with the one NSNull.
On a somewhat side note, if the leaf key is entirely unknown, then only NSNulls are returned:
5> d.valueForKeyPath("test.dog") as! NSArray
$R4: NSArray = "2 values" {
[0] = {
NSObject = {
isa = NSNull
}
}
[1] = {
NSObject = {
isa = NSNull
}
}
}
In contrast, if the root key is unknown, nil is returned:
6> d.valueForKeyPath("dog.name")
$R5: AnyObject? = nil
This logic strikes me as inconsistent, but perhaps I'm missing something?

var array:[AnyObject] = [1.1, 1.2, 1.3, 1.4, 1.5, 1.6, NSNull(),1.7, 1.8, 1.9]
let newArr = array.filter{ !($0 is NSNull) }
newArr

The second part of your question doesn't make sense to me:
This code:
let x = d.valueForKeyPath("dog.name")
Makes x an optional AnyObject?.
It returns nil with the key "dog.name" on your data. That's different than an array with nil/NSNULL entries.
If you try to force-unwrap it, it crashes:
let x = d.valueForKeyPath("dog.name") as! NSArray
If you want to get rid of the NSNull entries, use a filter:
let y = (d.valueForKeyPath("test.age") as? NSArray)?.filter{!($0 is NSNull)}
In the above code, I use as? to cast the result of valueForKeyPath to an array so it can return nil if the call does not return any results. (Otherwise it crashes.)
I then only call filter if the results are not nil.
Finally, I filter the array to only those objects that are not NSNull.
Note that y is an optional, and will be nil if d.valueForKeyPath("test.age") does not return a result.

Related

Access to key inside Dictionary get in Swift

Is it possible to access the key inside of a Dictionary get in Swift ?
The main idea is:
In this code
var _dict:[String:String] = [:]
var dict:[String:String] {
//get the key
return _dict
}
_dict = ["key 1":"Value 1","key 2":"Value 2"]
print(dict["key 1"])
Access the key to check if the value exists, if it exists return the value if not generate the value for that key
Did you know that Dictionary allows you to specify a default value in its subscript to avoid dealing with optional values. It works like so:
let dict = ["a": 1, "b": 2]
let c = dict["c", default: 3]
print(c) // 3
but that doesn't change the dictionary - it's still only has "a" and "b" keys, which is the expected behavior.
I think what you're asking about is whether it's possible to mutate the dictionary with a default value. The answer is yes - you could create a subscript with a mutating get.
But it's the wrong thing to do!
You will effectively have a getter with side-effects, which is typically a bad practice.
In any case, this is how you could implement a subscript with a new parameter setDefault:
extension Dictionary {
subscript(key: Key, setDefault defaultVal: #autoclosure () -> Value) -> Value {
mutating get {
if let val = self[key] {
return val
} else {
let val = defaultVal()
self[key] = val
return val
}
}
}
}
// dict needs to be a var now
var dict = ["a": 1, "b": 2]
let c = dict["c", setDefault: 3]
Now, this will mutate dict and it will be ["a": 1, "b": 2, "c": 3]

Sorting array of dictionaries by value after for loop?

I've seen a lot of answers to similar questions but none of the methods have worked so far.
if let users = snapshot.value!["users"] as? Dictionary<String,AnyObject> {
each.users = Int(users.count)
var pointsArray = [Dictionary<String,Int>]()
for (key, value) in users {
let uid = key
let points = value["points"] as! Int
pointsArray.append([uid : points])
}
I'm then needing to sort pointsArray by the "points", sort it from high to low, grab the 0th (highest) element, then grab the uid to use.
I've tried:
var myArr = Array(pointsArray.keys)
var sortedKeys = sort(myArr) {
var obj1 = dict[$0] // get ob associated w/ key 1
var obj2 = dict[$1] // get ob associated w/ key 2
return obj1 > obj2
}
This gives me
Value of type [ Dictionary <String,Int>] has no member keys.
I guess that's cause I'm trying to run the sort on my array of dicts vs the dicts themselves? How do I switch this up to get into the actual dictionaries vs. running the sort on the array?
Right - you're not properly accessing the keys of the dictionaries.
Here's a working code:
var pointsArray = [Dictionary<String, Int>]()
pointsArray.append(["1" : 10])
pointsArray.append(["2" : 45])
pointsArray.append(["3" : 30])
// sort by points
let sorted = pointsArray.sort({ $0.first!.1 > $1.first!.1 })
print(sorted) // [["2": 45], ["3": 30], ["1": 10]]
Array(pointsArray.keys) - this doesn't work, because pointsArray is an array, therefore it doesn't have keys property. The contents of pointsArray are dictionaries and they have keys. So you can access the keys of the first dictionary like this: pointsArray[0].keys

Swift - Cannot invoke initializer for type 'NSDictionary'

Since upgrading to Xcode7, I am getting the following error:
Cannot invoke initializer for type 'NSDictionary' with an argument list of type '(objects: [AnyObject!], forKeys: [String])'
on this line of code:
self.sessionBids!.addObject(NSDictionary(objects: [PFUser.currentUser().objectId, PFUser.currentUser().objectForKey("username"), self.bidTextField.text], forKeys: ["user", "name", "bid"]))
Can someone explain why?
EDIT: Here is the full block of code
if(self.bidTextField.text!.rangeOfString("^[0-9]*$", options: .RegularExpressionSearch) != nil) {
self.sessionBids = array[0].objectForKey("bids") as? NSMutableArray
var lastSessionBid : NSDictionary
SVProgressHUD.showProgress(50)
var previousHighBid : Int! = 0
if(self.sessionBids == nil) {
self.sessionBids = NSMutableArray()
} else {
lastSessionBid = self.sessionBids.objectAtIndex(self.sessionBids.count - 1) as! NSDictionary
previousHighBid = Int(lastSessionBid.objectForKey("bid") as! String)
}
if( previousHighBid >= Int(self.bidTextField.text!)) {
print("bid is lower than current bid")
SVProgressHUD.showErrorWithStatus("Bid is lower than current bid!")
return
} else {
self.sessionBids!.addObject(NSDictionary(objects: [PFUser.currentUser().objectId, PFUser.currentUser().objectForKey("username"), self.bidTextField.text], forKeys: ["user", "name", "bid"]))
SVProgressHUD.showProgress(75)
self.session.setObject(self.sessionBids, forKey: "bids")
self.session.save()
self.keyboardShowing = false
self.reloadSessionBids()
SVProgressHUD.showProgress(100)
SVProgressHUD.showSuccessWithStatus("Successfully Added Bid")
}
} else {
SVProgressHUD.showErrorWithStatus("Bid must be a number!")
}
You're trying to call init(objects: [AnyObject], forKeys keys: [NSCopying]) initializer of NSDictionary. objects: [AnyObject] can't contain Optionals (according to its declaration), and it seems that PFUser.currentUser().objectId, PFUser.currentUser().objectForKey("username"), self.bidTextField.text are all Optionals, that's why you're getting the error.
To resolve this, as vadian suggested, you'll need to unwrap all the Optionals in that array.
Both the objects and the keys of a dictionary must not be nil.
Make sure that all objects are non-optionals.
Try calling NSDictionary's
public init(objects: UnsafePointer<AnyObject?>, forKeys keys: UnsafePointer<NSCopying?>, count cnt: Int) instead:
self.sessionBids!.addObject(NSDictionary(objects: [PFUser.currentUser().objectId, PFUser.currentUser().objectForKey("username"), self.bidTextField.text], forKeys: ["user", "name", "bid"], count: 3))
Alternatively, try using a native dictionary instead of an NSDictionary.
Say:
var bidDict = [String : AnyObject]()
bid["user"] = PFUser.currentUser().objectId ?? "unknown"
bid["name"] = PFUser.currentUser().objectForKey("username") ?? "unknown"
bid["bid"] = self.bidTextField.text ?? "unknown"
Then insert into your array:
self.sessionBids!.addObject(bid)

My NSJSONSerialization seems like it's giving me nested NSDictionaries--how can I access specific keys within the outside key?

I hope I phrased my question properly, I apologize if I didn't. Not entirely sure what's going on.
So I parsed the JSON with
let jsonData:NSDictionary = NSJSONSerialization.JSONObjectWithData(responseData!, options: NSJSONReadingOptions.MutableContainers, error: &errors) as! NSDictionary
which produces:
{
"tests" = (
{
age = 50;
gender = m;
vehicle = 1;
},
{
age = 73;
gender = m;
vehicle = 14;
},
{
age = 50;
gender = m;
vehicle = 22;
},
{
age = 50;
gender = m;
vehicle = 23;
},
.
.
.
When I print (jsonData["tests"] I get basically the same thing as an NSArray, except no "tests {...}" --it looks virtually the same other than that.
I want to populate a tableView with the "vehicle" entry from each element, so how can I get an array or something usable with just the "vehicle" entry from each. The only "key" I get when printing allKeys with the original NSDictionary is "tests" and I can't cast jsonData["tests"] as an NSDictionary to make new keys like "age, gender..."
tl;dr how can I go from the NSDictionary "jsonData" to an array of vehicle numbers which looks like, [1, 14, 22, 23].
Any suggestions?
it looks like your json is a Dictionary that contains an Array of Dictionaries so you should be able to cast what is coming out of jsonData["tests"] as an Array and then use .map() to create an array of just the vehicle numbers like this:
if let dictArray = jsonData["tests"] as? [[String: AnyObject]] {
var vehicleArray = dictArray.map({ dict in
dict["vehicle"]
})
//Do something with vehicle array here
}
You have done work so far by unwrapping a Dictionary with a single key/value pair into just the value.
But then, you don't actually need or want to create a separate array from what you now have. Just use proper mapping within your table dataSource functions. Keep it as an array of dictionary objects.
if let a = jsonData["tests"] as? [[String: AnyObject]] {
myPersonArray = a
}
else { /* handle error */ }
// later
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// get the cell, then:
let vehicleNum = myPersonArray[indexPath.row]["vehicle"] as! Int
cell.text = "\(vehicleNum)"
// whatever else, then return the cell
}

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]

Resources