In my app I have a db with 50k+ contacts. To display them on list view, I need to calculate the index section titles to display them on the right side.
However my logic is taking time around 3-6 seconds to prepare the datasource.
-(NSArray *)getSectionTitleBasedOn:(NSString*)sortBy{
for (RealmContact *contact in contactSource){
if (contact.firstName.length>0) {
if ([sortBy isEqualToString:@"FirstName"]) {
NSString *firName= [contact.firstName stringByReplacingOccurrencesOfString:@" " withString:@""];
NSString *regex = @"^[A-Za-z]+";
NSPredicate *test = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", regex];
BOOL result = [test evaluateWithObject:firName];
if (contact.firstName.length>0 && result)
{
[nameDic setObject:@"firstletter" forKey:[[contact.firstName substringToIndex:1]uppercaseString]];
}else{
[nameDic setObject:@"firstletter" forKey:@"#"];
}
}
if ([sortBy isEqualToString:@"LastName"]) {
//same
}
if ([sortBy isEqualToString:@"Company"]) {
//same
}
}
}
NSLog(@"dic %@",nameDic);
return [[nameDic allKeys]sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
}
I'm wondering if there is anything I could do to improve this.
NSString *firName= [contact.firstName stringByReplacingOccurrencesOfString:@" " withString:@""];
NSString *regex = @"^[A-Za-z]+";
NSPredicate *test = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", regex];
BOOL result = [test evaluateWithObject:firName];
I'm doing this to check if string contain special chars or invalid chars.
After I get the array, I sort it and return the array. The output would be:
dic {
"#" = firstletter;
A = firstletter;
B = firstletter;
C = firstletter;
D = firstletter;
E = firstletter;
F = firstletter;
G = firstletter;
H = firstletter;
I = firstletter;
J = firstletter;
K = firstletter;
L = firstletter;
M = firstletter;
N = firstletter;
O = firstletter;
P = firstletter;
Q = firstletter;
R = firstletter;
S = firstletter;
T = firstletter;
U = firstletter;
V = firstletter;
W = firstletter;
X = firstletter;
Y = firstletter;
Z = firstletter;
}
I did this:
NSArray *sortedLetters = nil;
NSMutableSet *set = [[NSMutableSet alloc] init];
NSCharacterSet *charSet = [NSCharacterSet characterSetWithCharactersInString:@"AZERTYUIOPQSDFGHJKLMWXCVBN"];
for (RealmContact *contact in _dataSource)
{
NSString *firName = [[contact firstName] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
if ([firName length])
{
unichar c = [[firName uppercaseString] characterAtIndex:0];
if ([charSet characterIsMember:c])
{
[set addObject:[NSString stringWithFormat: @"%C", c]];
}
else
{
[set addObject:@"#"];
}
}
}
sortedLetters = [[set allObjects] sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
What are the differences?
• I used of a NSSet
instead of a NSDictionary
for the unicity of first letters. I find useless to use a NSDictionary
with a unused value and just for the unicity of the keys.
• I used stringByTrimmingCharactersInSet:
instead of stringByReplacingOccurrencesOfString:withString:
. In theory, the code of stringByTrimmingCharactersInSet:
should stop at the first char which isn't of the set, and not continue until the end of the string like stringByReplacingOccurrencesOfString:withString:
.
• I guess that characterIsMember:
is faster than a Regex/Predicate.
• Since you are only interested in uppercase, I translated it before doing the test, and not after, that could also speed the predicate in your case.
Also, small errors on your code:
if (contact.firstName.length>0 && result)
and
[nameDic setObject:@"firstletter" forKey:[[contact.firstName substringToIndex:1]uppercaseString]];
should use firName
instead of contact.firstName
.
On this test sample, my solution seems to be faster than yours (x10). I tested quickly on a OS X App (not iOS). I didn't do a lot of testings, but it may worth a try.
I don't know if it just for the question, but, the code optimization you could do:
NSString *stringToTest = nil;
if ([sortBy isEqualToString:@"FirstName"])
{
stringToTest = contact.firstName;
}
else if ([sortBy isEqualToString:@"LastName"])
{
stringToTest = contact.lastName;
}
else if ([sortBy isEqualToString:@"Company"])
{
stringToTest = contact.companyName;
}
stringToTest = [stringToTest stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
if ([stringToTest length])
{
//Do the code with stringToTest
}
It's for less code duplication and also use of if/else