What I am trying to achieve is to search an array for a string, here is the code for searching the array
if ([EssentialsArray containsObject:imageFilePath]) {
NSLog(#"YES");
}
else {
NSLog(#"NO");
NSLog(#"%#", ImageFilePath);
NSLog(#"%#", EssentialsArray);
}
The NSLogs return this for the imageFilePath:
/var/mobile/Applications/5051DC84-CAC8-4C1D-841D-5539A1E28CB1/Documents/12242012_11025125_image.jpg
And this for the EssentialsArray:
(
{
EssentialImage = "/var/mobile/Applications/5051DC84-CAC8-4C1D-841D-5539A1E28CB1/Documents/12242012_11025125_image.jpg";
}
)
And then obviously they return a "NO" value because the array couldn't find the string.
Thanks in advance
The issue is that the invocation of [EssentialsArray containsObject:imageFilePath] clearly assumes that EssentialsArray is an array of strings, whereas it's not. It's an array of dictionary entries with one key, EssentialImage.
There are at least two solutions. The first is to make an array of strings for those dictionary entries whose key is EssentialImage:
NSArray *essentialImages = [essentialsArray valueForKey:#"EssentialImage"];
if ([essentialImages containsObject:imagePath])
NSLog(#"YES");
else
NSLog(#"NO");
Depending upon the size of your essentialsArray (please note, convention dictates that variables always start with a lower case letter), this seems a little wasteful to create an array just so you can do containsObject, but it works.
Second, and better in my opinion is to use fast enumeration to go through your array of dictionary entries, looking for a match. To do this, define a method:
- (BOOL)arrayOfDictionaries:(NSArray *)array
hasDictionaryWithKey:(id)key
andStringValue:(NSString *)value
{
for (NSDictionary *dictionary in array) {
if ([dictionary[key] isEqualToString:value]) {
return YES;
}
}
return NO;
}
You can now check to see if your array of dictionaries has a key called #"EssentialImage" with a string value equal to the string contained by imagePath
if ([self arrayOfDictionaries:essentialsArray
hasDictionaryWithKey:#"EssentialImage"
andStringValue:imagePath])
NSLog(#"YES");
else
NSLog(#"NO");
Update:
You can also use predicates:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"EssentialImage contains %#", imagePath];
BOOL found = [predicate evaluateWithObject:array];
Related
Without unintentionally killing performance, does this appear at first glance to be acceptable for perhaps 200 guid strings in one list compared for equality with 100 guid strings from another list to find the matching indexes.
I have a method signature defined like so...
-(NSArray*)getItemsWithGuids:(NSArray*)guids
And I wanted to take that passed in array of guids and use it in conjunction with this array...
NSArray *allPossibleItems; // Has objects with a property named guid.
... to obtain the indexes of the items in allPossibleItems which have the matching guids from guids
My first instinct was to try indexesOfObjectsPassingTest but after putting together the block, I wondered whether the iOS framework already offers something for doing this type of compare more efficiently.
-(NSArray*)getItemsWithGuids:(NSArray*)guids
{
NSIndexSet *guidIndexes = [allPossibleItems indexesOfObjectsPassingTest:^BOOL(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop)
{
SomeObjWithGuidProperty *someObject = obj;
for (NSString *guid in guids) {
if ([someObject.guid isEqualToString:guid]) {
return YES;
}
}
return NO;
}];
if (guidIndexes) {
// Have more fun here.
}
}
Since you're working with Objective-C (not Swift) check out YoloKit. In your case, you can do something like:
guids.find(^(NSString *guid){
return [someObject.guid isEqualToString:guid];
});
My thought would be to use a set -
-(NSArray*)getItemsWithGuids:(NSArray*)guids inAllObjects:(NSArray *)allObjects
{
NSSet *matchGuids=[NSSet setWithArray:guids];
NSMutableArray *matchingObjects=[NSMutableArray new];
for (SOmeObjectWithGuidProperty *someObject in allObjects) {
if ([matchGuids contains:someObject.guid]) {
[matchingObjects addObject:someObject];
}
}
return [matchingObjects copy];
}
Your code looks like it would have O(n^2) performance, which is bad. I think the solution of converting guids to an NSSet and then using NSSet's containsObject would likely be much more performant. You could rewrite your indexesOfObjectsPassingTest code to use an NSSet and containsObject pretty easily.
If order doesn't matter much, I would suggest to change data structure here. Instead of using NSArray, consider to use NSDictionary with guid as key and someObject as value. In this case, you should use -[NSDictionary objectsForKeys:notFoundMarker:] method to obtain objects.
It will work much faster, than enumeration trough 2 arrays. If the NSDictionary key have a good hash function, accessing an element, setting an element, and removing an element all take constant time. NSString has good hash.
-(NSArray*)getItemsWithGuids:(NSArray*)guids {
NSArray *objectsAndNulls = [allPossibleItemsDictionary objectsForKeys:guids notFoundMarker:[NSNull null]];
if (objectsAndNulls) {
// Have more fun here.
// You should check that object in objectsAndNulls is not NSNull before using it
}
return objectsAndNulls;
}
UPD Unfortunately, there is no way to pass nil as notFoundMarker. If you can't provide usable notFoundMarker value and don't want to perform additional checks, you can query objects one by one and fill NSMutableArray. In this case you will avoid pass trough array to remove NSNulls:
-(NSArray*)getItemsWithGuids:(NSArray*)guids {
NSMutableArray *objects = [NSMutableArray arrayWithCapacity:guids.count];
for (NSString *guid in guids) {
SomeObjWithGuidProperty *object = allPossibleItemsDictionary[guid];
if (nil != object) {
[objects addObject:object];
}
}
if (nil != objects) {
// Have more fun here.
}
return object;
}
If got a NSMutableDictionary from Json Data
NSMutableDictionary *returnedDict = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
I know that this key returnedDict[#"data"][#"weather"][day][#"tides"] is NSNull in some cases. So I get -[NSNull objectForKeyedSubscript:]
So I try according to this answer How to check if an NSDictionary or NSMutableDictionary contains a key? to check if it is nil or not.
if (returnedDict[#"data"][#"weather"][day][#"tides"]){ some code }
and
if (returnedDict[#"data"][#"weather"][day][#"tides"]!=[NSNull null]){ some code}
does not avoid to run {some code}
How do I check this in the right way?
So your issue is:
Your server may return null to indicate that an object isn't present. NSJSONSerialization will convert that null into an instance of NSNull. In theory that means that instead of doing result[a][b][c] you need to check whether result[a] is a dictionary and, if so, whether result[a][b] is a dictionary, etc, etc, which is repetitious and error-prone?
Perhaps the easiest thing might be to remove from the dictionary any key with a value of NSNull, so that next time you ask for the value you'll get an ordinary nil, which is safe to message per the usual compound-messaging rules?
NSJSONSerialization won't do that for you but it's easy enough to add after the fact:
#interface NSDictionary(RemoveNullValues)
- (NSDictionary *)ty_collectionWithoutNullValues;
#end
#interface NSArray(RemoveNullValues)
- (NSArray *)ty_collectionWithoutNullValues;
#end
[...]
#implementation NSDictionary(RemoveNullValues)
- (NSDictionary *)ty_collectionWithoutNullValues {
NSMutableDictionary *reducedDictionary = [self mutableCopy];
// remove any keys for which NSNull is the direct value
NSArray *keysEvaluatingToNull = [self allKeysForObject:[NSNull null]];
[reducedDictionary removeObjectsForKeys:keysEvaluatingToNull];
// ask any child dictionaries to do the same; note that it's safe
// to mutate reducedDictionary in this array because allKeys is a
// copy property; what you're iterating is not reducedDictionary
// but a snapshot of its keys when the array first began
for (id key in [reducedDictionary allKeys]) {
id child = reducedDictionary[key];
if ([child respondsToSelector:#selector(ty_collectionWithoutNullValues)]) {
reducedDictionary[key] = [child ty_collectionWithoutNullValues];
}
}
return [reducedDictionary copy];
}
#end
#implementation NSArray(RemoveNullValues)
- (NSArray *)ty_collectionWithoutNullValues {
NSMutableArray *reducedArray = [NSMutableArray array];
for (id child in self) {
if ([child isKindOfClass:[NSNull class]]) continue;
if ([child respondsToSelector:#selector(ty_collectionWithoutNullValues)]) {
[reducedArray addObject:[child ty_collectionWithoutNullValues]];
} else {
[reducedArray addObject:child];
}
}
return [reducedArray copy];
}
#end
You must read this answer in conjunction with the accepted answer and comments to the question Is there NSMutableDictionary literal syntax to remove an element?
Following on from the linked answer you can quietly remove all the NSNull's and return nil instead if you access the element using the literal syntax (i.e. not using objectForKey:) by adding the following to your application:
#implementation NSDictionary (ClobberNSNull)
- (id) objectForKeyedSubscript:(id<NSCopying>)key
{
id result = [self objectForKey:key];
return result == NSNull.null ? nil : result;
}
#end
Now when you use the syntax:
dictionary[key]
if the matching object is NSNull then nil will be returned just as if the key did not exist.
There are caveats, see the linked question, and you need to decide if this approach is suitable for your situation. But it is simple.
HTH
Note: Before someone comments, NSNull is a singleton so the == is OK.
use
if(![returnedDict[#"data"][#"weather"][day][#"tides"] isKindOfClass:[NSNull class]]) { some code }
I am writing an app, and one of the features I need to implement requires the app to pull JSON data from a website, store it in a dictionary, then be able to use all of the keys and display the values. I will not know what the structure of the dictionary will look like, so I was hoping to recursively traverse the dictionary to retrieve all of the information.
I have the the JSON stored in a dictionary from the website that I need, and when I put the dictionary variable in a println() statement it displays correctly.
I found this link and I think this, or some variation of this should work, but I am still fairly new to swift and I am not sure how this translates from Objective-c to swift.
The part of that link that I am interested in is this:
(void)enumerateJSONToFindKeys:(id)object forKeyNamed:(NSString *)keyName
{
if ([object isKindOfClass:[NSDictionary class]])
{
// If it's a dictionary, enumerate it and pass in each key value to check
[object enumerateKeysAndObjectsUsingBlock:^(id key, id value, BOOL *stop) {
[self enumerateJSONToFindKeys:value forKeyNamed:key];
}];
}
else if ([object isKindOfClass:[NSArray class]])
{
// If it's an array, pass in the objects of the array to check
[object enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
[self enumerateJSONToFindKeys:obj forKeyNamed:nil];
}];
}
else
{
// If we got here (i.e. it's not a dictionary or array) so its a key/value that we needed
NSLog(#"We found key %# with value %#", keyName, object);
}
}
I'm not sure how to go about this, any help or pointers in the right direction are appreciate. Thanks!
EDIT: This is the direction I started to go in, but there were a lot of errors. I tried to fix them but didn't have much luck.
func enumerateJSONToFindKeys(id:AnyObject, keyName:NSString){
if id.isKindOfClass(NSDictionary)
{
AnyObject.enumerateKeysAndObjectsUsingBlock(id.key, id.value, stop:Bool())
{
self.enumerateJSONToFindKeys(id.value, forKeyNamed: keyName)
}
}
else if id.isKindOfClass(NSArray)
{
}
}
Try this:
func enumerateJSONToFindKeys(object:AnyObject, forKeyNamed named:String?) {
if let dict = object as? NSDictionary {
for (key, value) in dict {
enumerateJSONToFindKeys(value, forKeyNamed: key as? String)
}
}
else if let array = object as? NSArray {
for value in array {
enumerateJSONToFindKeys(value, forKeyNamed: nil)
}
}
else {
println("found key \(named) value \(object)")
}
}
It uses the Swift as? conditional casting operator as well as native iteration over both the NSDictionary and NSArray.
I have an NSArray of custom objects and would like to filter down that array to be unique on a specific key. Most of the things I've seen while searching for an answer involve using valueForKey:, valueForKeyPath: or #distinctUnionOfObjects but those return arrays of values for that key. I want the whole object instead.
The objects are subclassed PFObjects from Parse so they are KVC compliant, and I would like them to be filtered on the objectId key.
Put this in a category on NSArray:
-(NSArray*)arrayFilteredForUniqueValuesOfKeyPath:(NSString*)keyPath
{
NSMutableSet* valueSeen = [NSMutableSet new];
return [self filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
id value = [evaluatedObject valueForKeyPath:keyPath];
if(![valueSeen containsObject:value])
{
[valueSeen addObject:value];
return true;
}
else
{
return false;
}
}]];
}
Of course, the concept is kind of flawed since you really have no way of determining which of the n objects that have any give value for the keyPath you really wanted (in this case you get the first one)
The goal is to compare two arrays as and check if they contain the same objects (as fast as possible - there are lots of objects in the arrays). The arrays cannot be checked with isEqual: as they are differently sorted.
I already tried the solution posted here (https://stackoverflow.com/a/1138417 - see last code snippet of the post by Peter Hosey). But this doesn't work with differently sorted arrays.
The code I'm using now is the following:
+ (BOOL)arraysContainSameObjects:(NSArray *)array1 andOtherArray:(NSArray *)array2 {
// quit if array count is different
if ([array1 count] != [array2 count]) return NO;
BOOL bothArraysContainTheSameObjects = YES;
for (id objectInArray1 in array1) {
BOOL objectFoundInArray2 = NO;
for (id objectInArray2 in array2) {
if ([objectInArray1 isEqual:objectInArray2]) {
objectFoundInArray2 = YES;
break;
}
}
if (!objectFoundInArray2) {
bothArraysContainTheSameObjects = NO;
break;
}
}
return bothArraysContainTheSameObjects;
}
This works, but those are two nested fast enumerations. Is there a way to do a faster comparison?
As per your code, you are strict to same number of elements and each object of first array should be there in second array and vice versa.
The fastest way would be to sort both the array and compare them.
Ex:
NSArray *array1=#[#"a",#"b",#"c"];
NSArray *array2=#[#"c",#"b",#"a"];
array1=[array1 sortedArrayUsingSelector:#selector(compare:)];
array2=[array2 sortedArrayUsingSelector:#selector(compare:)];
if ([array1 isEqualToArray:array2]) {
NSLog(#"both have same elements");
}
else{
NSLog(#"both having different elements");
}
How about converting both arrays to sets and comparing them.
NSSet *set1 = [NSSet setWithArray:arr1];
NSSet *set2 = [NSSet setWithArray:arr2];
Compare the two using
if([set1 isEqualToSet:set2]) {
}
Use containsObject: method instead of iterating the whole array.
NSArray *array;
array = [NSArray arrayWithObjects: #"Nicola", #"Margherita", #"Luciano", #"Silvia", nil];
if ([array containsObject: #"Nicola"]) // YES
{
// Do something
}
like this
+ (BOOL)arraysContainSameObjects:(NSArray *)array1 andOtherArray:(NSArray *)array2 {
// quit if array count is different
if ([array1 count] != [array2 count]) return NO;
BOOL bothArraysContainTheSameObjects = YES;
for (id objectInArray1 in array1) {
if (![array2 containsObject:objectInArray1])
{
bothArraysContainTheSameObjects = NO;
break;
}
}
return bothArraysContainTheSameObjects;
}
Tried to get the accepted answer working but it wasn't quite the best fit for my situation.
I found this answer and all credit goes to #joel kravets for the method.
Basically sorting using a comparator enables you to sort using objects more easily - hence the problem I was facing when trying to use the above solution.
NSArray * array1 = [NSArray arrayWithArray:users];
NSArray * array2 = [NSArray arrayWithArray:threadUsers];
id mySort = ^(BUser * user1, BUser * user2){
return [user1.name compare:user2.name];
};
array1 = [array1 sortedArrayUsingComparator:mySort];
array2 = [array2 sortedArrayUsingComparator:mySort];
if ([array1 isEqualToArray:array2]) {
NSLog(#"both are same");
}
else{
NSLog(#"both are different");
}
Previously I had tried to use other answers like those above, using break to go through loops but in the end this answer came out easiest probably due to its speed and also that in the end we have the if statement allowing us to put code depending on if they are the same or different.
Thanks to Anoop for getting me on the right track and Joel for helping me to tighten the efficiency of it
If you want to check whether both arrays contain the same duplicates, just use NSCountedSet. It's like an NSSet, but each object in the set also has a count telling you how often it has been added. So
BOOL same = (array1.count == array2.count);
if (same && array.count > 0)
{
NSCountedSet* set1 = [[NSCountedSet alloc] initWithArray:array1];
NSCountedSet* set2 = [[NSCountedSet alloc] initWithArray:array2];
same = ([set1 isEqual: set2]);
}
No matter how you do it, this will be time consuming, so you might consider if there are special cases that can be handled quicker. Are these arrays usually the same, or almost the same, or is it true 99% of the time that they are different and that 99% of the time a random element of array1 is not in array2? Are the arrays often sorted? In that case, you could check whether there are identical objects in identical positions, and then consider only those objects that are not the same. If one array contains objects a, b, c, d, e and the other contains a, b, x, d, y, then you only need to compare the array [c, e] vs. [x, y].
[docTypes containsObject:#"Object"];
It will works for your req. As early as fast it will return boolean value for it.
This way the complexity is O(N^2), if you follow this approach you can't do it with a lower complexity. While instead you can do it with O(N log(N)) if you sort both arrays and then compare them. This way after having them sorted you will do it using isEqualToArray: in other N operations.
NSArray *filtered = [someArray filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"someParamter == %#", paramValue]]];
if (filtered.count) {
}
the main plus is you can use it for any kind of objects: custom, system, NSDictionary. for example I need to know is my UINavigationController's stack contains MySearchResultsVC and MyTopMenuItemsVC or not:
NSArray *filtered = [self.navigationController.viewControllers filteredArrayUsingPredicate:
[NSPredicate predicateWithFormat:#"class IN %#",
[NSArray arrayWithObjects:
[MySearchResultsVC class],
[MyTopMenuItemsVC class],
nil]]];
if (filtered) {
/* ok, now we can handle it! */
}
I know it's late but i just wanna share what i did..
NSString *stringArr1 = [NSString stringWithFormat:#"%#", array1];
NSString *stringArr2 = [NSString stringWithFormat:#"%#", array2];
if ([stringArr1 isEqual: stringArr2])
NSLog(#"identical");
else
NSLog(#"not");
this is just like comparing "#[#1,#2,#3,#4]" == "[#3,#2,#1,#4]".. which is obviously false..
i guess this will do:
[array1 isEqualToArray:array2];
returns bool;