Generating an NSDictionary from an SQL statement - objective-c

I am trying to generate an NSDictonary that can be used to populate a listview with data I retrieved from an SQL statement. when I go to create an array and add them it adds the arrays for ALL my keys and not just for the current key. I've tried a removeAllObjects on the array but for some reason that destroys ALL my data that I already put in the dictionary.
//open the database
if(sqlite3_open([dbPath UTF8String], &database) == SQLITE_OK)
{
const char *sql = "select alphaID, word from words order by word";
sqlite3_stmt *selectStatement;
//prepare the select statement
int returnValue = sqlite3_prepare_v2(database, sql, -1, &selectStatement, NULL);
if(returnValue == SQLITE_OK)
{
NSMutableArray *NameArray = [[NSMutableArray alloc] init];
NSString *alphaTemp = [[NSString alloc] init];
//loop all the rows returned by the query.
while(sqlite3_step(selectStatement) == SQLITE_ROW)
{
NSString *currentAlpha = [NSString stringWithUTF8String:(char *)sqlite3_column_text(selectStatement, 1)];
NSString *definitionName = [NSString stringWithUTF8String:(char *)sqlite3_column_text(selectStatement, 2)];
if (alphaTemp == nil){
alphaTemp = currentAlpha;
}
if ([alphaTemp isEqualToString:(NSString *)currentAlpha]) {
[NameArray addObject:definitionName];
}
else if (alphaTemp != (NSString *)currentAlpha) {
[self.words setObject:NameArray forKey:currentAlpha];
[NameArray removeAllObjects];
[NameArray addObject:definitionName];
}
}
}
The Statement above adds all the "keys" but then removes all the array elements for all keys. if I take out the removeAllKeys it adds ALL the array elements for ALL keys. I don't want this I want it to add the array elements FOR the specific key then move on to the next key.
in the end I want a NSDictonary with
A (array)
Alpha (string)
Apple (string)
B (array)
Beta (string)
Ball (string)
C (array)
Code (string)
...

Though I don't think it affects your problem, from the way I read your code, you should change
NSString *alphaTemp = [[NSString alloc] init];
to
NSString *alphaTemp = nil;
since alphaTemp is just used to point to an NSString that is generated initially as currentAlpha. You also should call [NameArray release] at some point below the code you've given, since you alloc'd it.
The real issue is that you are repeatedly adding pointers to the same NSMutableArray to your NSDictionary (self.words). I can see two ways to fix this:
Change
[self.words setObject:NameArray forKey:currentAlpha];
to
[self.words setObject:[NSArray arrayWithArray:NameArray] forKey:currentAlpha];
so that you are adding a newly-created (non-mutable) NSArray to your NSDictionary.
-- or --
Insert
[NameArray release];
NameArray = [[NSMutableArray alloc] init];
after
[self.words setObject:NameArray forKey:currentAlpha];
so that once you've inserted the NSMutableArray into the NSDictionary, you create a new NSMutableArray for the next pass.

Related

Browsing an object's fields

In Objective C, I have an object e.g. Person with a lot of fields firstName, lastName, phoneNumber, address, city... and so on. These fields types are NSString and any of these could be nil.
Now I want to concatenate my field values in another NSString :
Person *p = ...
NSMutableString *s = [[NSMutableString alloc] init];
for (NSString *field in #[p.firstName, p.lastName, p.phoneNumber,
p.adress, p.city, ....more fields...]) {
if ([field length] > 0) {
[s appendFormat:#"%#\n", field];
}
}
Issue is that this code crash whenever one of the field is nil. I have the exception :
[__NSPlaceholderArray initWithObjects:count:]: attempt to insert nil object
from objects[0]'
How could I handle simply the case of nil values within my for loop ?
I agree with #TomPace's post, for this small number I would do a simple if/else.
However, there may be times you do need to loop through a list of fields.
It's a bad idea to blindly pull the values into an array as you could be trying inserting nil values into the array. In this case, it would be better to place the field names into a key array as strings and loop through the list using valueForKey: to access the values. I would possibly store the keys list somewhere else where it can be used again.
Person *p = ...
NSMutableString *s = [[NSMutableString alloc] init];
NSArray *keys = #[#"firstName", #"lastName", #"phoneNumber", #"adress", #"city"];
for (NSString *key in keys)
{
NSString *value = [p valueForKey:key];
if ([value length] > 0) {
[s appendFormat:#"%#\n", value];
}
}
Person *person = [[Person alloc] init];
person.firstName = nil;
person.lastName = #"lastName";
NSMutableString *s = [[NSMutableString alloc] init];
[s appendFormat:#"%#\n", person.firstName == nil?#"":person.firstName];
[s appendFormat:#"%#\n", person.lastName == nil?#"":person.lastName];
For a selection of fields this small, don't use a for loop.
You may be saving a bit of code by attempting the for-loop structure, but it's really not the way to go if you're building the NSArray with only a few fields, and especially because you can't put nil items in it.
A better way to go is:
Person *p = ...
NSMutableString *s = [[NSMutableString alloc] init];
if ([p.firstName length] > 0) [s appendFormat:#"%#\n", p.firstName];
if ([p.lastName length] > 0) [s appendFormat:#"%#\n", p.lastName];
if ([p.phoneNumber length] > 0) [s appendFormat:#"%#\n", p.phoneNumber];
if ([p.adress length] > 0) [s appendFormat:#"%#\n", p.adress];
if ([p.city length] > 0) [s appendFormat:#"%#\n", p.city];
Edit, after original Question was updated with large amount of fields.
Like #BergQuester said, an approach to support a larger, arbitrary set of fields is using KVO-style inspection.
NSArray *fieldNames = #[#"firstName", #"lastName", #"phoneNumber", ....more fields...];
NSString *field;
for (NSString *fieldName in fieldNames) {
field = [p valueForKey:fieldName];
if ([field length] > 0 ) {
[s appendFormat: #"%#\n", field];
}
}
Try to create NSMutableString category
#import "NSMutableString+checkForNilObject.h"
#implementation NSMutableString (checkForNilObject)
-(void) appendNotNillObject:(NSString *) string
{
if(string)
{
[self appendString:string];
}
}
#end
You can override the getters of the class Person.
#implementation Person
- (NSString *)firstName{
if (_firseName == nil)
_firstName = #"";
return _firstName;
}
///....Other setters
#end
Like this you can define all your setters here.

addObject replaces the previous values

In the following code i am trying to add the user selected names in arrayDoctors, say A,B and then i add it to selectedDoctors array. But the 2nd time i select doctors C,D and add it to selectedDoctors array it replaces the previous objects A,B to C and D.
- (void)doneDoctorSelection:(id)sender
{
[pop3 dismissPopoverAnimated:YES];
NSString *str = [arrayDoctors objectAtIndex:0];
NSString *str1 = [str stringByAppendingString:[NSString stringWithFormat:#" + %d",arrayDoctors.count-1]];
if([arrayDoctors count] == 1)
lblDoctor.text = str;
else
lblDoctor.text = str1;
[selectedDoctors addObject:arrayDoctors];
[selectedDoctorIdList addObject:arrayDoctorsId];
NSLog(#"selectedDoneDoctors %# ",selectedDoctors);
}
What am i doing wrong?
Instead of this part
NSMutableArray *tempDoctor = [[NSMutableArray alloc]initWithArray:arrayDoctors];
[selectedDoctors addObject:tempDoctor];
You can use
[selectedDoctors addObject:[arrayDoctors mutableCopy]];
Ah!! finally found the solution. I was removing the objects from arrayDoctors before putting new values in it, and i had initialized it in viewDidLoad method. So what was happening is when i did [selectedDoctors addObject:arrayDoctors]; the second time it was replacing the same object at 1st place also since its uses the same memory location. The solution was to create a temporary object. The following is the changed code.
- (void)doneDoctorSelection:(id)sender
{
[pop3 dismissPopoverAnimated:YES];
NSString *str = [arrayDoctors objectAtIndex:0];
NSString *str1 = [str stringByAppendingString:[NSString stringWithFormat:#" + %d",arrayDoctors.count-1]];
NSMutableArray *tempDoctor = [[NSMutableArray alloc]initWithArray:arrayDoctors];
NSMutableArray *tempDocorId = [[NSMutableArray alloc]initWithArray:arrayDoctorsId
];
if([arrayDoctors count] == 1)
lblDoctor.text = str;
else
lblDoctor.text = str1;
NSLog(#"Before Adding %# ",selectedDoctors);
[selectedDoctors addObject:tempDoctor];
[selectedDoctorIdList addObject:tempDocorId];
//[selectedDoctors addObjectsFromArray:arrayDoctors];
//[selectedDoctorIdList addObjectsFromArray:arrayDoctorsId];
NSLog(#"selectedDoneDoctors %# ",selectedDoctors);
}
Thanx for pointing me in right direction.

All objects in array (interpreted from csv) being returned as the same object (the last object)

What I am trying to achieve, is to convert a csv file into an array of custom objects, however, my attempts at this seem to result in all of the objects in the array being returned as the same object (the last object in the array).
Before I explain further, here is the code:
- (NSArray *)arrayFromCSVFileName:(NSString *)csvFileName fileType:(NSString *)fileType {
// Convert the file into an NSData object
NSString *studentFilePath = [[NSBundle mainBundle] pathForResource:csvFileName ofType:fileType];
NSData *studentData = [NSData dataWithContentsOfFile:studentFilePath];
// Convert the NSData into an NSString
NSString *csvString = [[NSString alloc] initWithData:studentData encoding:NSUTF8StringEncoding];
// Split each record (line) in the csvDataString into an individual array element (split on the newline character \n)
NSArray *csvArray = [csvString componentsSeparatedByString:#"\n"];
// Create an array to hold the parsed CSV data
NSMutableArray *parsedCSVArray = [[NSMutableArray alloc] init];
NSMutableArray *elementArray = [[NSMutableArray alloc] init];
CGSElement *elementToAdd = [[CGSElement alloc] init];
// Loop through each line of the file
for (int i = 0; i < [csvArray count]; i++) {
// Get a reference to this record (line) as a string, and remove any extranous new lines or alike
NSString *csvRecordString = [[csvArray objectAtIndex:i] stringByReplacingOccurrencesOfString:#"\r" withString:#""];
// Split the line by the comma delimeter
NSArray *csvRecordArray = [csvRecordString componentsSeparatedByString:#","];
// Check that there are actually fields (i.e. this is not a blank line)
if ( ([csvRecordArray count] > 0) && ([[csvRecordArray objectAtIndex:0] length] > 0) ) {
elementToAdd.mass = [[csvRecordArray objectAtIndex:1] floatValue];
elementToAdd.atomicNumber = [[csvRecordArray objectAtIndex:0] intValue];
elementToAdd.name = [csvRecordArray objectAtIndex:2];
elementToAdd.symbol = [csvRecordArray objectAtIndex:3];
elementToAdd.period = [[csvRecordArray objectAtIndex:4] intValue];
[elementArray addObject:elementToAdd];
}
}
for (int i = 0; i < [elementArray count]; i++) {
NSLog(#"%i", i);
CGSElement *current = [elementArray objectAtIndex:i];
NSLog(#"Name = %#", current.name);
}
// Return the parsed array
return elementArray;
}
The custom object in question is the CGSElement object, which I am attempting to fill the elementArray with. However, my debug code (the following section of code):
for (int i = 0; i < [elementArray count]; i++) {
NSLog(#"%i", i);
CGSElement *current = [elementArray objectAtIndex:i];
NSLog(#"Name = %#", current.name);
}
Is resulting, rather than in the return of all of the correct element names, it is returning the last element (to put this in context, ununoctium), 118 times.
After some testing, I can safely say that up until after this point:
elementToAdd.mass = [[csvRecordArray objectAtIndex:1] floatValue];
elementToAdd.atomicNumber = [[csvRecordArray objectAtIndex:0] intValue];
elementToAdd.name = [csvRecordArray objectAtIndex:2];
elementToAdd.symbol = [csvRecordArray objectAtIndex:3];
elementToAdd.period = [[csvRecordArray objectAtIndex:4] intValue];
All of the elements are being correctly defined, rather than just the same element over and over.
Needless to say, I'm stumped as to why it would be returning the same object over and over. Any help would be appreciated.
This line:
CGSElement *elementToAdd = [[CGSElement alloc] init];
Should be inside your loop, just before you try to edit the object and add it to the array. Currently you are repeatedly mutating the same object instead of creating new objects for each record.
You add the same entity all the time. It is crated once before the loop and within the loop it values are changed again and angan and it is added to the array. Naturally all items in the aray carry the same values because it is the same object.
If you want then change the array with an NSSet. To a set an object can only added once and you will end up with a set of 1. That is not the solution of couse, it would just visualize what is happening.
To solve it move this line
CGSElement *elementToAdd = [[CGSElement alloc] init];
to the beginning of the body of the for i loop, so that a new instance is created for every iteration and therefore for every index of the array.

improve fast enumeration performance

I have a huge word list of over 280.000+ words that is loaded from an sqlite database to an NSArray. then I do a fast enumeration to check if a certain string value entered by the user matches one of the words in the Array. Since the array is so large it takes about 1-2 seconds on the iphone 4 to go through that array.
How can I improve the performance? Maybe I should make several smaller arrays? one for each letter in the alphabet so that there is less data to go through.
this is how my database class looks
static WordDatabase *_database;
+(WordDatabase *) database
{
if (_database == nil) {
_database = [[WordDatabase alloc] init];
}
return _database;
}
- (id) init
{
if ((self = [super init])) {
NSString *sqLiteDb = [[NSBundle mainBundle] pathForResource:#"dictionary" ofType:#"sqlite"];
if (sqlite3_open([sqLiteDb UTF8String], &_database) != SQLITE_OK) {
NSLog(#"Failed to open database!");
}
}
return self;
}
- (NSArray *)dictionaryWords {
NSMutableArray *retval = [[[NSMutableArray alloc] init] autorelease];
NSString *query = #"SELECT word FROM words";
sqlite3_stmt *statement;
if (sqlite3_prepare_v2(_database, [query UTF8String], -1, &statement, nil) == SQLITE_OK) {
while (sqlite3_step(statement) == SQLITE_ROW) {
char *wordChars = (char *) sqlite3_column_text(statement, 0);
NSString *name = [[NSString alloc] initWithUTF8String:wordChars];
name = [name uppercaseString];
[retval addObject:name];
}
sqlite3_finalize(statement);
}
return retval;
}
then in my main view I initialise it like this
dictionary = [[NSArray alloc] initWithArray:[WordDatabase database].dictionaryWords];
and finally I go through the array using this method
- (void) checkWord
{
NSString *userWord = formedWord.wordLabel.string;
NSLog(#"checking dictionary for %#", userWord);
for (NSString *word in dictionary) {
if ([userWord isEqualToString: word]) {
NSLog(#"match found");
}
}
}
Lots of different ways.
stick all the words in a dictionary or set, testing for presence is fast
break it up as you suggest; create a tree type structure of some kind.
use the database to do the search. They are generally pretty good at exactly that, if constructed correctly.
If space isn't an issue, store a hash value of each word and use that for your base lookup. Once filtered by the hash, then compare each of the words. This will reduce the number of costly string comparisons. Easier to index/sort and performs quick lookups.
I second a dictionary. NSDictionary for objective c.
for instance:
// To print out all key-value pairs in the NSDictionary myDict
for(id key in myDict)
NSLog(#"key=%# value=%#", key, [myDict objectForKey:key]);

Different Keys Point to Same Object(s) in NSMutableDictionary

I have a custom object called Person that among other things contains an NSString field called descriptor, which stores what sort of person that Person object is (angry, sad, wild, happy, morose, etc). All of my Person objects are in an NSMutableArray, but I would like to store them in an NSMutableDictionary in such a manner:
Key: A, Object: An NSMutableArray where all Person objects have descriptor starting with 'A'
Key: B, Object: An NSMutableArray where all Person objects have descriptor starting with 'B'
Key: C, Object: An NSMutableArray where all Person objects have descriptor starting with 'C'
etc...
I've tried to do this in my code below, and at the comment //POINT 1, the keys and arrays seem to match up, but at //POINT 2, when I print out the complete dictionary, all the keys come up with the same values!
So I wanted to know why the NSMutableArray I seem to have is not being stored as I want it in the NSMutableDictionary?
- (void)buildDictionaryForIndexList {
NSMutableDictionary *tempDict = [[[NSMutableDictionary alloc] init] autorelease];
NSMutableArray *personsStartingWithLetter = [[NSMutableArray alloc] init];
NSMutableArray *indexList = [[[NSMutableArray alloc] init] autorelease];
NSInteger loopCounter = 1;
NSString *firstLetter = [[[NSString alloc] init] autorelease];
for (Person *v in persons) {
firstLetter = [[v descriptor] substringWithRange:NSMakeRange(0, 1)];
if ([indexList containsObject:firstLetter]) {
[personsStartingWithLetter addObject:v];
if (loopCounter == [persons count]) {
[tempDict setObject:personsStartingWithLetter forKey:firstLetter];
}
} else {
if (loopCounter > 1) {
//POINT 1
NSLog(#"%#",[indexList objectAtIndex:[indexList count]-1]);
for (Person *q in personsStartingWithLetter) {
NSLog(#"%#",[q descriptor]);
}
[tempDict setObject:personsStartingWithLetter forKey:[indexList objectAtIndex:([indexList count] - 1)]];
[personsStartingWithLetter removeAllObjects];
}
[indexList addObject:firstLetter];
[personsStartingWithLetter addObject:v];
} // else
loopCounter++;
} // for
//POINT 2
NSEnumerator *enumerator = [tempDict keyEnumerator];
for (NSString *str in enumerator) {
NSLog(#"%#",str);
for (Person *c in [tempDict objectForKey:str]) {
NSLog(#"%#",[c descriptor]);
}
}
self.dictionary = tempDict;
} // buildDictionaryForIndexList
So, for example, at POINT 1 my output is:
A
Angry
Amiable
B
Belligerent
C
Cool
...
W
Wild
but at POINT 2 my output is
T
Wild
J
Wild
A
Wild
...
W
Wild
Change [tempDict setObject:personsStartingWithLetter forKey:[indexList objectAtIndex:([indexList count] - 1)]]; (just after point 1) to [tempDict setObject:[[personsStartingWithLetter copy] autorelease] forKey:[indexList objectAtIndex:([indexList count] - 1)]];. The problem is that NSDictionary copies the key, but retains the value. Therefore, if you add a mutable array to the dictionary and then change it, the array in the dictionary also changes. You need to create a non-mutable copy of the array to put in the dictionary.
The whole method is a bit overcomplicated.
- (void)buildDictionaryForIndexList
{
NSMutableDictionary *tempDict = [[[NSMutableDictionary alloc] init] autorelease];
for (Person *v in persons)
{
NSString* firstLetter = [[v descriptor] substringWithRange:NSMakeRange(0, 1)];
NSMutableArray* personsStartingWithLetter = tempDict [firstLetter];
if (personsStartingWithLetter == nil)
{
personsStartingWithLetter = [NSMutableArray array];
tempDict [firstLetter] = personsStartingWithLetter;
}
[personsStartingWithLetter addObject:v];
} // for
self.dictionary = tempDict;
}
You start with an empty dictionary that will contain arrays. For every person, you check whether there is a suitable array or not, and if there isn't one, you create it. So now there is an array for the person, so you add it to the array. That's all.