I have one NSArray with objects {3, 5, 6} and a NSDictionary with {3, 5} i want to compare those and delete the "6" because its missing from the NSDictionary i'm using the following code
[lists enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
Item *item = (Item *)obj;
NSString *value = [NSString stringWithFormat:#"%#", item.remoteID];
for (NSDictionary *dictionary in responseObject)
{
NSString *idValue = [dictionary objectForKey:#"id"];
if ([value isEqualToString:idValue])
{
NSLog(#"value %#", value);
}
}
}];
NSLog(#"value %#", value);
here i'm getting the correct values but i want to get the missing values in my case "6"
any ideas welcome.
If I understand correctly, you have an array lists and an array responseObject of dictionaries, which contain an id key with the object you want to compare to the array lists.
NSArray *lists = #[#3, #5, #6];
NSArray *responseObject = #[#{#"id":#3},#{#"id":#5}];
Then, you could do something like this to get "the missing values":
NSArray *idValues = [responseObject valueForKey:#"id"];
//Subset of objects in lists that are not present in idValues:
NSMutableArray *result = [NSMutableArray arrayWithArray:lists];
[result removeObjectsInArray:idValues];
There are several ways to go about this.
If you wish to look at it as a filtering task in which you filter the lists array by the id values in the responseObject, then a simple predicate will do:
NSPredicate *responsePred = [NSPredicate predicateWithFormat: #"self IN %#", [responseObject valueForKey: #"id"]];
NSArray *result = [lists filteredArrayUsingPredicate: responsePred];
Related
I know I can check if a string contains another string like this
NSString *string = #"hello bla bla";
if ([string rangeOfString:#"bla"].location == NSNotFound) {
NSLog(#"string does not contain bla");
} else {
NSLog(#"string contains bla!");
}
But what if I have an NSArray *arary = #[#"one",#"two", #"three", #"four"] and I wanted to check if a string contains either one of these without just loop or have a bunch of or's (|| ). So it would be something like this
if (array contains one or two or three or four) {
//do something
}
But if I have a longer array this becomes tedious so is there another way, without just looping through?
EDIT
I want to check if myArray has any of theses values in valuesArray
valuesArray =#[#"one",#"two", #"three", #"four"];
myArray = [#"I have one head", #"I have two feet", #"I have five fingers"]
OUTPUT
outputArray = #[#"I have one head", #"I have two feet"]
There you go:
NSArray* arrRet = [myArray filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id __nonnull evaluatedObject, NSDictionary<NSString *,id> * __nullable bindings) {
for(NSString* val in valuesArray) {
if ([evaluatedObject rangeOfString:val].location != NSNotFound)
return true;
}
return false;
}]];
arrRet contains exactly the two desired strings.
A little bit more magic later you have your code without writing a loop :P
NSArray* arrRet = [myArray filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary<NSString *,id> * bindings) {
BOOL __block match = false;
[valuesArray enumerateObjectsUsingBlock:^(id __nonnull obj, NSUInteger idx, BOOL * __nonnull stop) {
*stop = match = [evaluatedObject rangeOfString:obj].location != NSNotFound;
}];
return match;
}]];
You could use a NSCompoundPredicate
NSCompoundPredicate *predicate = [NSCompoundPredicate orPredicateWithSubpredicates:subPredicates];
Where your subPredicates must look like
(
SELF CONTAINS[c] "one",
SELF CONTAINS[c] "two",
SELF CONTAINS[c] "three",
SELF CONTAINS[c] "four"
)
To get there from
NSArray *array = #[#"one", #"two", #"three", #"four"]
You could use a for loop, but as you are opposed to that, let's cheat:
by using a category I each NSArray functional mapping, but instead of looping, I use enumerating
#interface NSArray (Map)
-(NSArray *) vs_map:(id(^)(id obj))mapper;
#end
#implementation NSArray (Map)
-(NSArray *)vs_map:(id (^)(id))mapper
{
NSMutableArray *mArray = [#[] mutableCopy];
[self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
id mapped = mapper(obj);
[mArray addObject:mapped];
}];
return [mArray copy];
}
#end
Now I can create the subPredicates like
NSArray *subPredicates = [arary vs_map:^id(NSString *obj) {
return [NSPredicate predicateWithFormat:#"SELF contains[c] %#", obj];
}];
and create the compound predicate like
NSCompoundPredicate *predicate = [NSCompoundPredicate orPredicateWithSubpredicates:subPredicates];
and use it
BOOL doesContain = [predicate evaluateWithObject:string];
et voilĂ : No (obvious) looping, while there is one hidden in the enumeration and probably in the predicate as-well.
Now with the changed question you basically ask for filtering. You can use the same predicate for that:
NSArray *testarray = #[#"I have one head", #"I have two feet", #"I have five fingers"];
NSArray *arary = #[#"one",#"two", #"three", #"four"];
NSArray *subPredicates = [arary vs_map:^id(NSString *obj) {
return [NSPredicate predicateWithFormat:#"SELF contains[c] %#", obj];
}];
NSCompoundPredicate *predicate = [NSCompoundPredicate orPredicateWithSubpredicates:subPredicates];
NSArray *results = [testarray filteredArrayUsingPredicate:predicate];
results now contains
(
I have one head,
I have two feet
)
the complete code
#import <Foundation/Foundation.h>
#interface NSArray (Map)
-(NSArray *) vs_map:(id(^)(id obj))mapper;
#end
#implementation NSArray (Map)
-(NSArray *)vs_map:(id (^)(id))mapper
{
NSMutableArray *mArray = [#[] mutableCopy];
[self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
id mapped = mapper(obj);
[mArray addObject:mapped];
}];
return [mArray copy];
}
#end
int main(int argc, const char * argv[]) {
#autoreleasepool {
NSArray *testarray = #[#"I have one head", #"I have two feet", #"I have five fingers"];
NSArray *arary = #[#"one",#"two", #"three", #"four"];
NSArray *subPredicates = [arary vs_map:^id(NSString *obj) {
return [NSPredicate predicateWithFormat:#"SELF contains[c] %#", obj];
}];
NSCompoundPredicate *predicate = [NSCompoundPredicate orPredicateWithSubpredicates:subPredicates];
NSArray *results = [testarray filteredArrayUsingPredicate:predicate];
}
return 0;
}
Besides my cheating the my other question, here an idea how really to avoid time costly looping: Use Set computation magic!
created a class 'Sentence', instantiate it with the strings to test
created a 'Word' class that takes a word to search for
overwrite both classes' isEqual: method to match for if a word is in the sentence (use sets there too!)
put those into an array.
from this array create a NS(*)Set object
put all word in a set
execute union on it.
I've a dictionary with a string value and an int key.
i.e.
{1,abc}
{2,bcd}
{3,cde}
I'm filtering it using NSPredicate as follows
NSMutableDictionary *tableDataSearch;
NSArray *searchResults;
NSPredicate *resultPredicate = [NSPredicate predicateWithFormat:#"self contains[cd] %#", searchText];
searchResults = [[tableDataSearch allValues] filteredArrayUsingPredicate:resultPredicate];
it is returning me the array of values which contain the specific word. I want all keys to be returned in the array and search in values like it is searching right now.
any help ?
suppose you have an array of dictionaries myArrayOfDict. and dictionary have differentes keys. this will with gives you all dictionnary where any value contains your string:
NSMutableArray* myArrayOfDict = [#[#{#"key1":#"value1"} , #{#"key2": #"value2"}]mutableCopy];
NSPredicate *resultPredicate = [NSPredicate predicateWithFormat:#"ANY SELF.#allValues contains[cd] %#", #"lue1"];
NSArray *searchResults = [myArrayOfDict filteredArrayUsingPredicate:resultPredicate]; // will get the 2nd diction
The following code filters an NSDictionary based on a substring search on its values:
NSDictionary *data = #{#1 : #"abc",
#2 : #"bcd",
#3 : #"def"};
NSString *searchText = #"bc";
NSMutableDictionary *filteredData = [NSMutableDictionary new];
[data enumerateKeysAndObjectsUsingBlock:^(id key, NSString *obj, BOOL *stop) {
NSRange range = [obj rangeOfString:searchText
options:NSCaseInsensitiveSearch | NSDiacriticInsensitiveSearch];
if (range.location != NSNotFound)
{
[filteredData setObject:obj forKey:key];
}
}];
NSArray *keys = [filteredData allKeys];
If this feels a bit cumbersome, BlocksKit provides some useful extensions to NSDictionary:
NSDictionary *filteredData = [data bk_select:^BOOL(id key, NSString *obj) {
NSRange range = [obj rangeOfString:searchText
options:NSCaseInsensitiveSearch | NSDiacriticInsensitiveSearch];
return range.location != NSNotFound;
}];
And if you prefer to use NSPredicate, you can replace range.location != NSNotFound with:
[resultPredicate evaluateWithObject:obj];
After a network call to the Instagram API, I get back a responseDictionary NSDictionary delegate with the following Key/Value structure:
{
data = (
{
bio = "Los Angeles/Orange County Realtor\U00ae \n\U6d1b\U6749\U77f6\U623f\U5730\U4ea7\U7ecf\U7eaa\U4eba\nCall/Text/WhatsApp: (310) 717-1321\nEmail: Jxxxcom\nWeChat (\U5fae\U4fe1): xx";
"full_name" = "xx yy (\U7530\U4f73\U6dfc) Rx Realty";
id = 25354408;
"profile_picture" = "http://scontent-a.cdninstagram.com/hphotos-xpa1/outbound-distillery/t0.0-20/OBPTH/profiles/profile_xxx_75sq_1391378894.jpg";
username = jxxi;
website = "http://www.Jxghty.com";
},
The profile_picture key often has an NSString value that contains anonymousUser (for the users who didn't set any profile pictures).
I am looking to remove those entries from my responseDictionary as follows:
//Create mutable copy of IG responseDictionary
NSMutableDictionary *dictCleanAvatars = [responseDictionary mutableCopy];
NSLog(#"Log dictCleanAvatars after mutableCopy IG response: %#", dictCleanAvatars);
NSArray *keys = [dictCleanAvatars allKeys]; //get all the keys
NSUInteger k2 = [dictCleanAvatars count];
NSLog(#"k2 in dictCleanAvatars before cleanup is: %lu", (unsigned long)k2);
for (int i = 0; i<k2; i++)
{
if ([[dictCleanAvatars objectForKey:[keys objectAtIndex:i]] isKindOfClass:[NSString class]])
{
//if its an NSString - don't want an exception if its another type of object
NSLog(#"Yes, objectAtIndex:i us Kind ofClass NSString for i = %d", i);
if ([[dictCleanAvatars objectForKey:[keys objectAtIndex:i]] rangeOfString:#"anonymousUser"].location != NSNotFound)
{
NSLog(#"Yes, anonymousUser identified in objectAtIndex:i for i = %d", i);
//if object has the key word im looking for
[dictCleanAvatars removeObjectForKey:[keys objectAtIndex:i]]; //remove the key
NSLog(#"That's dictCleanAvatars after loop %d: %#", i, dictCleanAvatars);
}
}
}
But this doesn't work.
Would value feedback from more experience iOS developers.
If you're trying to build an array that includes everything from the data key's array, but omitting those dictionaries for which profile_picture contains the string "AnonymousUser", you can use NSPredicate:
NSArray *dataArray = responseDictionary[#"data"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"not (profile_picture contains 'AnonymousUser')"];
NSArray *filteredArray = [dataArray filteredArrayUsingPredicate:predicate];
Or you can use predicateWithBlock:
NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(NSDictionary *evaluatedObject, NSDictionary *bindings) {
return [evaluatedObject[#"profile_picture"] rangeOfString:#"AnonymousUser"].location == NSNotFound;
}];
BTW, if you already have a mutable array, you can also remove entries from it using filterUsingPredicate, using the above predicates:
NSMutableArray *mutableDataArray = [responseDictionary[#"data"] mutableCopy];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"not (profile_picture contains 'AnonymousUser')"];
[mutableDataArray filterUsingPredicate:predicate];
If, on the other hand, you don't want to remove entire dictionaries from the array of dictionaries, but rather want to simply remove the occurrences of profile_picture for which "AnonymousUser" is present, you want to ensure that not only is the array mutable, but so are its constituent dictionaries.
The easiest way of doing this is to specify the NSJSONReadingMutableContainers option when parsing the JSON. Then you can just iterate through the NSMutableDictionary entries, removing the profile_picture entries with a profile_picture with "AnonymousUser" in them:
NSMutableDictionary *responseDictionary = [NSJSONSerialization JSONObjectWithData:responseData options:NSJSONReadingMutableContainers error:&error];
NSMutableArray *mutableDataArray = responseDictionary[#"data"];
for (NSMutableDictionary *dictionary in mutableDataArray) {
NSString *profilePicture = dictionary[#"profile_picture"];
if ([profilePicture rangeOfString:#"AnonymousUser"].location != NSNotFound) {
[dictionary removeObjectForKey:#"profile_picture"];
}
}
If, however, you can't specify the NSJSONReadingMutableContainers option when you parse the JSON and are stuck with a immutable collection, you need to make a mutable copy of it. Unfortunately, a simple mutableCopy of the array won't make the member dictionaries mutable themselves, but you can use a Core Foundation call to CFPropertyListCreateDeepCopy to make a mutable array with mutable entries, which you can then modify:
NSMutableArray *mutableDataArray = CFBridgingRelease(CFPropertyListCreateDeepCopy(kCFAllocatorDefault, (CFArrayRef)responseDictionary[#"data"], kCFPropertyListMutableContainers));
Then you can use the above for loop, iterating through this array's dictionary entries, removing the offending profile_picture entries.
if [[dictCleanAvatars objectForKey:[keys objectAtIndex:i]] isEqualToString#"anonymousUser"] {
The problem is, suppose [dictCleanAvatars objectForKey:[keys objectAtIndex:i]] is not an NSString? You might want to check for that first.
If the only field you are looking at is profile_picture, I would go with a less generic approach which is much more readable and understandable
This code works for me
- (void)testExample
{
NSDictionary *dictionary = #{ #"data": #[ #{ #"bio": #"blah blah", #"profile_picture": #"some stuff anonymousUser other stuff" },
#{ #"bio": #"some other object", #"profile_picture": #"some other profile picture link" }] };
// dictionary is a mock of the data you provided
NSArray *data = [dictionary objectForKey:#"data"];
for (NSDictionary * avatarDict in data) {
NSMutableDictionary *mdict = [avatarDict mutableCopy];
id ppid = [mdict objectForKey:#"profile_picture"];
if ([ppid isKindOfClass:[NSString class]]) {
NSString *pp = (NSString *)ppid;
if ([pp rangeOfString:#"anonymousUser"].location != NSNotFound) {
[mdict removeObjectForKey:#"profile_picture"];
}
}
NSLog(#"altered dictionary: %#", mdict);
}
}
Output:
2014-08-13 10:53:36.727 test[11981:60b] altered dictionary: {
bio = "blah blah";
}
2014-08-13 10:53:36.728 test[11981:60b] altered dictionary: {
bio = "some other object";
"profile_picture" = "some other profile picture link";
}
I am new to Objective C. In my application, I am having array of data, in which I need only positive numbers and need to delete negative numbers.
result = [NSMutableArray arrayWithObjects: #"1",#"2",#"3","-4","-5","6","9",nil];
NSLog(#" Before Remove %d", [result count]);
NSString *nullStr = #"-";
[result removeObject:nullStr];
How to achieve this? Any pointers?
You can use a predicate to filter the array
NSArray * numbers = #[#"1", #"2", #"3", #"-4", #"-5", #"6", #"9"];
NSPredicate * predicate = [NSPredicate predicateWithFormat:#"integerValue >= 0"];
NSArray * positiveNumbers = [numbers filteredArrayUsingPredicate:predicate];
Result
[#"1", #"2", #"6", #"9"]
Also note that this will work with both an array of NSNumbers and an array of NSStrings, since they both feature the integerValue method.
the one you wrote is an array of strings, if that's ok you can loop the array and remove strings that starts with - using
Since you cannot remove objects while iterating, you can create a new array and store only positive numbers (or mark item to be deleted and delete after the loop)
NSMutableArray onlyPositives = [[NSMutableArray alloc] init]
for(int i=0; i < [result count]; i++)
{
if(![result[i] hasPrefix:#"-"])
[onlyPositives add:[result[i]]
}
Try This:
for(NSString *number in [result copy])
{
if([number intValue] < 0)
{
[result removeObject:number];
}
}
If you have an array and you want to filter items out of it, then using an NSPredicate is a good way to do it.
You haven't said whether your array contains NSNumbers or NSStrings, so here's a demonstration of how to use predicates to filter an array in both cases
// Setup test arrays
NSArray *stringArray = #[#"-1", #"0", #"1", #"2", #"-3", #"15"];
NSArray *numberArray = #[#(-1), #0, #1, #2, #(-3), #15];
// Create the predicates
NSPredicate *stringPredicate = [NSPredicate predicateWithFormat:#"integerValue >= 0"];
NSPredicate *numberPredicate = [NSPredicate predicateWithFormat:#"SELF >= 0"];
// Filtering the original array returns a new filtered array
NSArray *filteredStringArray = [stringArray filteredArrayUsingPredicate:stringPredicate];
NSArray *filteredNumberArray = [numberArray filteredArrayUsingPredicate:numberPredicate];
// Just to see the results, lets log the filtered arrays.
NSLog(#"New strings %#", filteredStringArray); // 0, 1, 2, 15
NSLog(#"New strings %#", filteredNumberArray); // 0, 1, 2, 15
This should get you started.
NSIndexSet *indexesOfNegativeNumbers = [result indexesOfObjectsPassingTest:^(id obj, NSUInteger idx, BOOL *stop) {
return [(NSString *)obj hasPrefix:#"-"];
}];
[result removeObjectsAtIndexes:indexesOfNegativeNumbers];
I am trying to find the tags inside a NSDictionary inside myAr that matches the criteria of str and I want the result that has only those exact arrays no more nor less. In this example I want only the 2nd NSDictionary of myAr.
I though of trying to achieve this by using a predicate but that always returns empty when i use arrays.
I am trying to filter using an array but this is not working. I was wondering if anyone could tell me what i am doing wrong and how could i achieve my objective. thanks in advance
NSArray * myAr = #[ #{ #"tags": #[#"one",#"two",#"three"],
#"number": #"4"
},
#{ #"tags": #[#"one",#"two"],
#"number":#"4"
},
#{ #"tags": #[#"one",#"two",#"four"],
#"number":#"4"
},
#{ #"tags": #[#"chacho",#"chocho"],
#"number":#"4"
},
#{ #"tags": #[#"one"],
#"number":#"4"
} ];
NSArray* str = #[#"one",#"two"];
NSPredicate* pre = [NSPredicate predicateWithFormat:#"tags CONTAINS %# ",str];
myAr = [myAr filteredArrayUsingPredicate:pre];
NSLog(#"%#",myAr);
If I understand your question correctly, you just have to replace "CONTAINS" by "="
in the predicate:
[NSPredicate predicateWithFormat:#"tags = %# ",str]
This gives an array with all dictionaries where the "tags" value is equal to the
given array str. In your example, it returns an array with the second dictionary
only.
UPDATE: To find all dictionaries where the "tags" value is an array with the
given elements, but independent of the order, the following slightly more
complicated predicate should work:
[NSPredicate predicateWithFormat:#"tags.#count = %d AND SUBQUERY(tags, $t, $t in %#).#count = %d",
[str count], str, [str count]];
UPDATE 2: That was too complicated, the following predicate seems to work as well:
[NSPredicate predicateWithFormat:#"tags.#count = %d AND ALL tags in %#",
[str count], str]
(I have assumed that str contains only different elements.)
For an answer that uses neither a for loop nor predicate format strings, try using a block and make use of NSSet to determine if the set of tags you want to match is equal to a set of the array element's tags. For example:
NSSet* desiredTags = [NSSet setWithObjects:#"one", #"two", nil];
NSPredicate *tagFilterPredicate = [NSPredicate
predicateWithBlock:^BOOL (id data, NSDictionary *bindings) {
NSSet *tags = [NSSet setWithArray:[data objectForKey:#"tags"]];
return [desiredTags isEqual:tags];
}];
NSArray *resultArray = [myArr filteredArrayUsingPredicate:tagFilterPredicate];
Bear in mind that this does allocate a set per iteration. So, if you're looking to avoid allocations, this is not adequate. Otherwise, it at least avoids a format string.
A brute-force way to do this would be to remove your predicate and just enumerate:
NSArray *required = #[#"one", #"two"];
NSMutableArray *matches = [#[] mutableCopy];
[myAr enumerateObjectsUsingBlock:^(NSDictionary *dict, NSUInteger idx, BOOL *stop) {
[dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
if ([obj isKindOfClass:[NSArray class]]) {
BOOL match = YES;
for (NSString *item in required) {
if (![obj containsObject:item]) {
match = NO;
break;
}
}
if (match && [(NSArray *)obj count] == required.count) {
[matches addObject:obj];
}
}
}];
}];