Is there an easy way to do a case insensitive lookup in an NSArray of NSStrings? Reference for NSArray mentions sorting case insensitively but nothing about lookup.
I can easily write my own fn to do it but would like to know if there's an easier way.
I don't know of any built-in way to do this. However, it would be trivial to write a category on NSArray which does this:
#interface NSArray (CaseInsensitiveIndexing)
- (NSUInteger)indexOfCaseInsensitiveString:(NSString *)aString;
#end
#implementation NSArray (CaseInsensitiveIndexing)
- (NSUInteger)indexOfCaseInsensitiveString:(NSString *)aString {
NSUInteger index = 0;
for (NSString *object in self) {
if ([object caseInsensitiveCompare:aString] == NSOrderedSame) {
return index;
}
index++;
}
return NSNotFound;
}
#end
Of course, you'd probably want to do a bit of type checking to make sure the array's items actually are NSStrings before you call -caseInsensitiveCompare:, but you get the idea.
questioner ,
it's an excellent idea of writing a category in NSArray to do this .
it helped me a lot in my app.
However there's a pretty much easier way to do the this instead
of iterating the array.
#interface NSArray (CaseInsensitiveIndexing)
- (NSUInteger)indexOfCaseInsensitiveString:(NSString *)aString;
#end
#implementation NSArray (CaseInsensitiveIndexing)
- (NSUInteger)indexOfCaseInsensitiveString:(NSString *)aString
{
return [self indexOfObjectPassingTest:^(id obj, NSUInteger idx, BOOL *stop)
{
return [[obj lowercaseString] isEqualToString:[aString lowercaseString]];
}];
}
#end
Note :indexOfObjectPassingTest works with IOS 4.0 only
No custom category needed:
[myArray indexOfObjectPassingTest:^(NSString *obj, NSUInteger idx, BOOL *stop){
return (BOOL)([obj caseInsensitiveCompare:term] == NSOrderedSame);
}]
I haven't tried it but you should be able to do this by filtering the array with an NSPredicate.
Related
I have my own class defined as below.
#interface PersonList : NSObject
#property(nonatomic, strong)NSNumber *ID;
#property(nonatomic, strong)NSString *FirstName;
#property(nonatomic, strong)NSString *SecondName;
#end
I use it like the following method:
PersonList *P = [[PersonList alloc]init];
[P setID: ...];
[P setFirstname:...];
[P setSecondname:...];
then add it to an array.
[PersonListArray addObject:P];
What I'm trying to do is search this array for the class where ID = x.
Is it the best way?
for(int i = 0; i < PersonListArray.count; i++)
{
PersonListArray *aPersonListArray = [PersonListArray objectAtIndex:i];
if(aPersonListArray.ID == x)
{
//Do what i want here
//break;
}
}
Thanks
You can use this NSArray method that makes things a lot easier and is also very optimized:
- (NSUInteger)indexOfObjectPassingTest:(BOOL (^)(id obj, NSUInteger idx, BOOL *stop))predicate
Your code should then look like that:
NSInteger personIndex = [PersonListArray indexOfObjectPassingTest:^BOOL(PersonList person, NSUInteger idx, BOOL *stop) {
return [person.ID isEqualToNumber:x];
}];
PersonList personList = PersonListArray[personIndex]
Two more things:
you might consider not capitalizing your variables, to follow conventions.
If you want to compare values of objects in ObjC, use the equalTo methods, not the == sign which is for comparing pointers
Hope this helps,
There is another way, a little bit more simple:
for(PersonList *AnyPerson in PersonListArray)
{
if([AnyPerson.ID isEqualToNumber:x])
{
//do what you want
}
}
You could do this like this:
for(PersonList *person in PersonListArray){
if([person.ID isEqualToNumber: x]){
// do your job, it you want to do it for the first case only
// use break here or return depends on the case
}
}
Take a look at the way of comparing values (if you want sth more than equality consider usage of compare: method)
BTW It might be valuable for you to take a look on the possibilities of sorting and searching arrays in case of possibilities and performance, take a look at this.
Try this
#interface PersonList ()
#property (nonatomic, strong) NSMutableArray *persons;
#end
#implementation PersonList
-(NSMutableArray *)persons{
static dispatch_once_t onceToken;
dispatch_once(&onceToken,^{
_persons=[[NSMutableArray alloc] init];
});
return _persons;
}
-(instancetype)initWithIDs:(NSArray *)personIDs FirstNames:(NSArray *)firstNames SecondNames:(NSArray *)secondNames{
if(self=[super init]){
[personIDs enumerateObjectsUsingBlock:^(id personID, NSUInteger idx, BOOL *stop) {
NSMutableDictionary *person=[[NSMutableDictionary alloc] init];
[person setObject:personID forKey:#"ID"];
[person setObject:[firstNames objectAtIndex:idx] forKey:#"FIRSTNAME"];
[person setObject:[secondNames objectAtIndex:idx] forKey:#"SECONDNAME"];
[self.persons addObject:person];
}];
}
return self;
}
-(NSDictionary *)findPersonByID:(NSString *)personID{
__block NSDictionary *dictionary=[[NSDictionary alloc] init];
[self.persons enumerateObjectsUsingBlock:^(id person, NSUInteger idx, BOOL *stop) {
if ([[person objectForKey:#"ID"] isEqualToString:personID]) {
dictionary=person;
*stop=YES;
}
}];
return dictionary;
}
#end
I'm a developer from Python world used to using exceptions. I found in many places that using exceptions is not so wise here, and did my best to convert to NSErrors when needed. but then I encounter this:
NSMutableArray *results;
for (NSDictionary *dict in dicts)
{
// Memory management code omitted
SomeModel *model = [[SomeModel alloc] init];
model.attr1 = [[dict objectForKey:#"key1"] integerValue];
model.attr2 = [[dict objectForKey:#"key2"] integerValue];
model.attr3 = [[dict objectForKey:#"key3"] integerValue];
model.attr4 = [[dict objectForKey:#"key4"] integerValue];
[results addObject:model];
}
with some of the objects in dict containing NSNull, which would result an "unrecognized selector" exception. In that case, I want to drop that datum completely. My first instinct is to wrap the whole content of the for block into a #try-#catch block:
NSMutableArray *results;
for (NSDictionary *dict in dicts)
{
#try
{
SomeModel *model = [[SomeModel alloc] init];
model.attr1 = [[dict objectForKey:#"key1"] integerValue];
model.attr2 = [[dict objectForKey:#"key2"] integerValue];
model.attr3 = [[dict objectForKey:#"key3"] integerValue];
model.attr4 = [[dict objectForKey:#"key4"] integerValue];
[results addObject:model];
}
#catch(NSException *exception)
{
// Do something
}
}
But is this a good approach? I can't come up with a solution without repeating checks on each variable, which is really ugly IMO. Hopefully there are alternatives to this that haven't occur to me. Thanks in advance.
The proper Objective-C way to do this would be:
for (NSDictionary *dict in dicts)
{
if (! [dict isKindOfClass:[NSDictionary class]])
continue;
// ...
}
Testing if a receiver can respond to a message before sending it is a typical pattern in Objective-C.
Also, take note that exceptions in Objective-C are always a programmer error and are not used for normal execution flow.
Many people use a category on NSDictionary for these cases:
- (id)safeObjectForKey:(id)aKey
{
id obj = [self objectForKey:aKey];
if ([obj isKindOfClass:[NSNull class]])
{
return nil;
}
return obj;
}
You still need to make sure, that your dict is an actual dictionary instance.
In the end I decided to solve the problem using KVC. Something like this:
- (id)initWithPropertyDictionary:(NSDictionary *)dict
lookUpTable:(NSDictionary *)keyToProperty
{
self = [self init];
for (NSString *key in dict)
{
NSString *propertyName;
if ([keyToProperty objectForKey:key])
propertyName = [keyToProperty objectForKey:key];
else
propertyName = key;
if ([[dict objectForKey:key] isKindOfClass:[NSNull class]])
{
[self release];
return nil;
}
else
{
[self setValue:[dict objectForKey:key] forKey:propertyName];
}
}
}
The setback of this resolution is that I'll have to use NSNumber for my properties, but for JSON data there is really no distinction between floating numbers and integers, so this is fine.
And if you really want primitive types, you can couple this method with custom setters that converts those NSNumbers into appropriate types.
With this, all you need to do is check for nil before adding the object into the array. Much cleaner everywhere except the model class.
Thanks to jaydee3 for inspiring me to focus on changing the model class.
Since I use NSInteger arrays frequently, I wrote a category for NSArray (and one for NSMutableArray too) that adds methods such as integerAtIndex:, arrayByAddingInteger:, etc. The methods take care of wrapping/unwrapping the NSInteger in an NSNumber object.
What I'm wondering is whether there is a way I can enhance my category so that I can do fast enumeration on the NSIntegers. I would like to be able to write:
NSArray* arrayOfIntegers;
.
.
.
for(NSInteger nextInteger in arrayOfIntegers)
{
}
….so that "nextInteger" is pulled out of the NSNumber object behind the scenes. Can I do this?
I doubt that there is a clean way of doing this with NSFastEnumeration, as it heavily depends on the nextObject method.
But, you could do it in another way, by adding a block method for it:
#interface NSArray (Integers)
-(void)eachInteger:(void(^)(NSInteger))block;
#end
#implementation NSArray (Integers)
-(void)eachInteger:(void(^)(NSInteger))block {
for (NSNumber *num in self) {
block(num.integerValue);
}
}
#end
That way, you could use it in your code in a similar way:
NSArray *arr = [NSArray arrayWithObjects:[NSNumber numberWithInt:23],
[NSNumber numberWithInt:42],
nil];
...
[arr eachInteger:^(NSInteger i) {
NSLog(#"The int is %i", i);
}];
// =>
// The int is 23
// The int is 42
Perhaps you might want to take a look at the NSArray categories on the Lumumba Framework, which happens to be written by me :D
This exactly cannot be done, but you can easily convert your NSNumber into an NSInteger and use that later on. You can even write a macro for it:
#define int_enum(var, arr, block) \
for(NSNumber *__tmp in arr) { NSInteger var = [__tmp integerValue]; block }
Use it like:
NSArray *array = // whatever;
int_enum(counter, array, {
// things you want to do with `counter' as an NSInteger
});
if you really like blocks, try this out:
#interface NSArray(blockIteration)
#property (copy, nonatomic, readonly) void (^forEachObject)(void (^block)(NSArray *, id));
#property (copy, nonatomic, readonly) void (^forEachInt)(void (^block)(NSArray *, int));
#property (copy, nonatomic, readonly) void (^forEachDouble)(void (^block)(NSArray *, double));
#end
#implementation NSArray(blockIteration)
-(void (^)(void (^)(NSArray *, id))) forEachObject
{
return [^(void (^block)(NSArray *, id)) {
block = [block copy];
for (id obj in self)
{
block(self, obj);
}
} copy];
}
-(void (^)(void (^)(NSArray *, int))) forEachInt
{
return [^(void (^block)(NSArray *, int)) {
block = [block copy];
for (NSNumber *num in self)
{
block(self, [num intValue]);
}
} copy];
}
-(void (^)(void (^)(NSArray *, double))) forEachDouble
{
return [^(void (^block)(NSArray *, double)) {
block = [block copy];
for (NSNumber *num in self)
{
block(self, [num doubleValue]);
}
} copy];
}
#end
int main()
{
NSArray *array = [NSArray arrayWithObjects:#"Hello", #"World", #"This", #"Is", #"A", #"Test", nil];
array.forEachObject(^(id arr, id obj) {
NSLog(#"%#", obj);
});
}
Note that this implementation is ARC dependent.
I have a category of NSMutableArray which has a deletion method:
#interface NSMutableArray(MyCategory)
- (void) deleteItemsMatchingCriteria: (SomeCriteria) theCriteria;
#end
How should this be implemented?
To iterate through arrays I usually use enumerateObjectsUsingBlock: but of course one cannot delete an item from the array while in the middle of the iteration.
Is there a canonical way of doing this for arrays in general, and does it differ if the method doing the deletion is a category of the array?
The simplest way to do it would be to use indexesOfObjectsPassingTest: method:
[self removeObjectsAtIndexes:[self indexesOfObjectsPassingTest:
^BOOL (id element, NSUInteger i, BOOL *stop) {
return /* YES if the object needs to be removed */;
}]
];
Obviously in the code below where i check for theCriteria you'll have more logic to determine if the object should be removed.
-(void)deleteItemsMatchingCriteria:(BOOL)theCriteria{
NSMutableIndexSet *remove = [[NSMutableIndexSet alloc] init];
[self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
if(theCriteria){
[remove addIndex:idx];
}
}];
[self removeObjectsAtIndexes:remove];
}
I have multiple strings combinations that I want my isEqualtoString to find automatically.
Right now, I have all combinations manually searched.
if([aString isEqualToString:#"xyz"] || [aString isEqualToString:#"zxy"] || [aString isEqualToString:#"yzx"] || [aString isEqualToString:#"xzy"] etc...){}
If you just want to know if any of them match, you can put all your candidates (xyz, zxy, ...) in an NSArray and call containsObject:aString on the array. Use indexOfObject:aString if you need to know which string was matched.
You can write a NSString category that does the job:
#interface NSString (isEqualToAnyStringAddition)
- (BOOL)isEqualToAnyString:(NSString *)firstString, ... NS_REQUIRES_NIL_TERMINATION;
#end
#implementation NSString (isEqualToAnyStringAddition)
- (BOOL)isEqualToAnyString:(NSString *)firstString, ...
{
if([self isEqualToString:firstString])
return YES;
va_list arguments;
va_start(arguments, firstString);
NSString *string;
while((string = va_arg(arguments, NSString *)))
{
if([self isEqualToString:string])
{
va_end(arguments);
return YES;
}
}
va_end(arguments);
return NO;
}
#end