NSPredicate instead of loop to filter an array of objects - objective-c

I have been told that I can use NSPredicate to duplicate the results of this method
- (void) clearArrayOut
{
bool goAgain = false;
for (int j=0; j<[array count]; j++)
{
if ([[array objectAtIndex:j] someMethod] == NO)
{
[array removeObjectAtIndex:j];
goAgain = true;
break;
}
}
if (goAgain) [self clearArrayOut];
}
How can I make an NSPredicate that will filter an array based on the results of some method of a custom class's call?

To make a copy with the filter applied:
NSArray *filteredArray = [someArray filteredArrayUsingPredicate:
[NSPredicate predicateWithBlock:^(id object, NSDictionary *bindings) {
return [object someMethod]; // if someMethod returns YES, the object is kept
}]];
To filter an NSMutableArray in place:
[someMutableArray filterUsingPredicate:
[NSPredicate predicateWithBlock:^(id object, NSDictionary *bindings) {
return [object someMethod]; // if someMethod returns YES, the object is kept
}]];
But I would probably just use a for loop if I were filtering a small array. However, I'd write my for loop a little differently to avoid having to either decrement the index variable or call myself recursively:
- (void)clearArrayOut:(NSMutableArray *)array {
for (int i = array.count - 1; i >= 0; --i) {
if (![[array objectAtIndex:i] someMethod]) {
[array removeObjectAtIndex:i];
}
}
}

You simply write it into your predicate, for example, lets assume you have an object with a method called isOdd and you want to filter your array to include only objects that return true for isOdd, you can do this:
#import <Foundation/Foundation.h>
#interface barfoo : NSObject
{
int number;
}
- (BOOL)isOdd;
- (id)initWithNumber:(int)number;
#end
#implementation barfoo
- (NSString *)description
{
return [NSString stringWithFormat:#"%i", number];
}
- (BOOL)isOdd
{
return (number % 2);
}
- (id)initWithNumber:(int)tnumber
{
if((self = [super init]))
{
number = tnumber;
}
return self;
}
#end
int main(int argc, char *argv[]) {
#autoreleasepool {
NSMutableArray *array = [NSMutableArray array];
for(int i=0; i<10; i++)
{
barfoo *foo = [[barfoo alloc] initWithNumber:i];
[array addObject:[foo autorelease]];
}
NSLog(#"%#", array); // prints 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"isOdd == true"]; // This is oure predicate. isOdd must be true for objects to pass
NSArray *result = [array filteredArrayUsingPredicate:predicate];
NSLog(#"%#", result);
}
}
Of course this also works the other way around, your predicate could also read isOdd == false or you can add even more requirements for an object to pass. Eg isOdd == true AND foo == bar. You can read more about the NSPredicate syntax in the NSPredicate documentation.

Your implementation is terribly inefficient to start with: rather than continuing the deletions recursively, you could change your loop to not advance if an object has been deleted, like this:
- (void) clearArrayOut {
int j = 0;
while (j < [array count]) {
if ([[array objectAtIndex:j] someMethod] == NO) {
[array removeObjectAtIndex:j];
} else {
j++;
}
}
}
You could do the same thing using filterUsingPredicate:, like this:
- (void) clearArrayOut {
NSPredicate *p = [NSPredicate predicateWithBlock:^BOOL(id obj, NSDictionary *bindings) {
return [obj someMethod] == NO
}];
[array filterUsingPredicate:p];
}

Related

Objective-C Why is this not working?

#import <Foundation/Foundation.h>
int main(int argc, const char * argv[])
{
#autoreleasepool {
NSMutableString *outputStringSet = [[NSMutableString alloc] init];
NSMutableString *outputStringArray = [[NSMutableString alloc] init];
NSMutableSet *mySet = [[NSMutableSet alloc] init];
NSMutableArray *myArray = [[NSMutableArray alloc] initWithCapacity: 10];
int userInput;
NSLog(#"Enter 10 numbers");
for( int i = 0; i < 10; i++) {
scanf("%i", &userInput);
NSNumber *input = [[NSNumber alloc] initWithInt: userInput];
[myArray addObject:input];
if([mySet member: input]) {
[mySet addObject: input];
}
}
for (int k = 0; k < [myArray count]; k++) {
[outputStringArray appendFormat:#"%#, ", [myArray objectAtIndex:k]];
}
NSLog(#"%#", [outputStringArray substringToIndex:[outputStringArray length] - 2]);
for (int j = 0; j < [myArray count]; j++) {
if([mySet containsObject: [myArray objectAtIndex:j]]) {
[outputStringSet appendFormat:#"%#, ", [myArray objectAtIndex:j]];
}
NSLog(#"%#", outputStringSet);
}
}
return 0;
}
Code above prints the array but not the appropriate object in the set
Why?
Please explain clearly. I am a bit of a noob, and couldnt find the answer anywhere else.
thanks
if([mySet member: input]) {
[mySet addObject: input];
}
You're adding the object to the set if it’s already in it. You want the reverse: add the object if it's not in it.
Thus:
if ( ! [mySet member:input] )
[mySet addObject:input];
By the way, you should use containsObject: instead of member: in your test:
containsObject:
Returns a Boolean value that indicates whether a given
object is present in the set.
- (BOOL)containsObject:(id)anObject
Edit: you don't even need to test if the object is already in the set before adding it. After all, that's the main purpose of a NSSet: to ensure uniqueness of its objects. So if you add an object twice, the second call will silently be ignored, as the object is alreay in it.
Your set is empty because of
if([mySet member: input]) {
[mySet addObject: input];
}
member:
Determines whether the set contains an object equal to a given object,
and returns that object if it is present.

Objective-C NSMutableArray

I have filled a NSMutableArray with integer and string values from my database.
The problem is that many values were inserted more than once.
Using the following code I remove duplicate objects
for (id object in originalArray) {
if (![singleArray containsObject:object]) {
[singleArray addObject:object];
}
}
Bus this works only if the objects are exactly the same between them.
Is there a way to remove duplicates based on the integer value?
EDIT (from an OP's comment on a deleted answer)
I have some objects containing int and NSString. For example #"John 13", #"Mary 25", #"Luke 25", #"Joan 13". The NSMutableArray will contain all four names and duplicates of 13, 25. I want to remove the duplicates leaving 13 and 25 only once in the array. I do not care which names will be removed. Care only for the integer values to use them later.
If your elements are all NSNumber objects:
for (int i=0;i<array.count;i++) {
for (int j=i+1;j<array.count;j++) {
if ([array[i] isEqualToNumber:array[j]]) {
[array removeObjectAtIndex:j--];
}
}
}
Or if all objects are either integer NSNumbers or NSStrings containing integer values:
for (int i=0;i<array.count;i++) {
for (int j=i+1;j<array.count;j++) {
if ([array[i] intValue] == [array[j] intValue]) {
[array removeObjectAtIndex:j--];
}
}
}
Try this:
// singleArray is initially empty
for (id object in originalArray)
{
BOOL contains= YES;
for( id single in singleArray)
{
if( [single integerValue]==[object integerValue] )
{
contains= NO;
break;
}
}
if(contains)
{
[singleArray addObject: object];
}
}
no test, tell me if it does not work. assuming objects in the array are string and format is "WORD NUMBER"
Boolean myEqual(const void *value1, const void *value2) {
NSString *str1 = (__bridge NSString *)(value1);
NSString *str2 = (__bridge NSString *)(value2);
NSArray *arr1 = [str1 componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
NSArray *arr2 = [str2 componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
return [[arr1 lastObject] isEqual:[arr2 lastObject]];
}
CFHashCode myHash(const void *value) {
NSString *str1 = (__bridge NSString *)(value);
NSArray *arr1 = [str1 componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
return [[arr1 lastObject] hash];
}
NSMutableArray *array = // your array;
CFSetCallBacks callBacks = kCFTypeSetCallBacks;
callBacks.equal = myEqual;
callBacks.hash = myHash;
CFMutableSetRef set = CFSetCreateMutable(NULL, [array count], &callBacks);
for (id obj in [array copy]) { // copy so can modify the original array
if (CFSetContainsValue(set, (__bridge const void *)(obj))) {
[array removeObject:obj];
} else {
CFSetAddValue(set, (__bridge const void *)(obj));
}
}

out of range in objectAtIndex objective c

The count of children_ (CCArray) outputs 15 and I'm receiving error:
'NSInternalInconsistencyException', reason: 'index out of range in objectAtIndex(14), index 15'
for (NSInteger i=[children_ count]-1; i>=0; i++) {
CCNode *c = [children_ objectAtIndex:i];
if ([c isKindOfClass:[CCLabelTTF class]]) {
[c removeFromParentAndCleanup:YES];
}
}
How would I solve this? Trying to remove all the labels in order to change their string value.
On my CCLayer I have have also some CCMenuItemLabel and CCMenuItemLabelAndSprite...
It looks like you want to iterate backwards through the collection class, so you need to perform i-- to modify the index variable:
for (NSInteger i=[children_ count]-1; i>=0; i--) {
CCNode *c = [children_ objectAtIndex:i];
if ([c isKindOfClass:[CCLabelTTF class]]) {
[c removeFromParentAndCleanup:YES];
}
}
you should use fast enumerating if possible:
for (id obj in [childres_ reverseObjectEnumerator]){
if ([obj isKindOfClass:[CCLabelTTF class]]) {
[obj removeFromParentAndCleanup:YES];
}
}
or with the block syntax
[children_ enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id obj, NSUInteger idx, BOOL *stop){
if ([obj isKindOfClass:[CCLabelTTF class]]) {
[obj removeFromParentAndCleanup:YES];
stop= YES;
};
}];
It seems that you are initializing your for loop counter with count-1 and incrementing, so the first value of i would be 14 , and the next one 15 (out of range)
Try this :
for (NSInteger i=0; i<[children_ count]; i++) {
}

Obj-C, function to return index of nsstring found in nsarray?

I've been looking to see if I can find a function like indexOf.
I have an array.
self.data = [[NSArray alloc] initWithObjects:#"apple", #"lemon", #"pear", nil];
Looking for a function to return which would look for lemon and return 1 ?
how about indexOfObject:?
NSUInteger index = [self.data indexOfObject:#"lemon"];
Try this method.
-(int)indexOfString:(NSString *)string inArray:(NSArray *)array {
for(int i=0; i<[array count]; i++) {
if ([[array objectAtIndex:i] class] == [NSString class]) {
if ([[array objectAtIndex:i] isEqualToString:string]) {
return i;
}
}
}
}
EDIT: The other answer using -indexOfObject: is better.

printing an array of arrays in objective C

Sorry for the simple question, but I am self taught and know that there are gaps in my education.
To print an array in objective C, I believe is:
NSLog(#"My array: %#", myArray);
How can I print an array of arrays?
Thanks
You want this:
for(NSArray *subArray in myArray) {
NSLog(#"Array in myArray: %#",subArray);
}
This will work for an array that has arrays nested one level deep.
You don't need to do anything different to log an array of arrays; the code exactly as you've written it will already show the contents of the sub-arrays.
That is, the following program:
#import <Foundation/Foundation.h>
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
NSMutableArray *array = [NSMutableArray array];
for (int i=0; i<5; ++i) {
NSMutableArray *sub = [NSMutableArray array];
for (int j=0; j<=i; ++j) {
[sub addObject:[NSString stringWithFormat:#"%d", j]];
}
[array addObject:sub];
}
NSLog(#"Array: %#", array);
[pool drain];
return 0;
}
Produces the following output:
Array: (
(
0
),
(
0,
1
),
(
0,
1,
2
),
(
0,
1,
2,
3
),
(
0,
1,
2,
3,
4
)
)
Clearly, it's already logging the sub-arrays just fine. If you want to control the formatting differently, you'd have to manually iterate them, but by default, the -description of an NSArray is little more than the -description of every object in that array, which includes all sub-arrays.
So I was embarrassed by the recursiveDescription thing, so I wrote my own as a category on NSArray. Note that this code will print out a description for an array of arrays to any depth. The description itself could probably use a bit more formatting than commas and newlines. Here you go:
#interface NSArray (RecursiveDescription)
- (NSString *)recursiveDescription;
#end
#implementation NSArray (RecursiveDescription)
- (NSString *)recursiveDescription {
NSMutableString *description = [[NSMutableString alloc] initWithString:#"Array (\n"];
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
for (NSObject *child in self) {
if ([child respondsToSelector:#selector(recursiveDescription)]) {
[description appendFormat:#"%#,\n", [child recursiveDescription]];
}
else {
[description appendFormat:#"%#,\n", [child description]];
}
}
[pool drain];
[description appendString:#"\n)"];
return [description autorelease];
}
#end
Try logging the return value from NSArray's -description method.
NSLog(#"My array: %#", [myArray description]);
Moreover, for print all of elements
int i = 0;
int j = 0;
for(NSArray *subArray in myArray) {
NSLog(#"[%d] %#",i, subArray);
j =0;
for(NSObject *element in subArray) {
NSLog(#"[%d:%d] %#", i,j,element);
++j;
}
++i;
}
As much as I like how easy it is to log out an object in Objective-C, I didn't like seeing a 2D array as a very long list. I created a category on NSArray that prints out 2D arrays. It's not perfect and can be improved, but it has worked for me.
Header:
#interface NSArray (Logging)
- (void)log2DArray;
#end
Implementation:
#import "NSArray+Logging.h"
#implementation NSArray (Logging)
- (void)log2DArray {
NSMutableString *formattedString = [[NSMutableString alloc] init];
NSInteger longestSubarrayLength = 0;
for (NSArray *subarray in self) {
if (subarray.count > longestSubarrayLength) {
longestSubarrayLength = subarray.count;
}
}
for (int i = 0; i < longestSubarrayLength; i++) {
[formattedString appendFormat:#"\n"];
for (int j = 0; j < self.count; j++) {
NSArray *tempArray = [self objectAtIndex:j];
if (tempArray.count <= longestSubarrayLength) {
[formattedString appendFormat:#"%#\t", [tempArray objectAtIndex:i]];
} else {
[formattedString appendFormat:#"\t"];
}
}
}
NSLog(#"%#", formattedString);
}
#end
Usage:
[myArray log2DArray];
Or use recursiveDescription :)
NSLog(#"my arrays: %#", [myArray recursiveDescription]);