removing null from arrays in Object-c - objective-c

I have this snipped of code that results in an array with a whole bunch of "<null>" throughout and I need to figure out how to remove them. Obviously after smashing my head against the keyboard I'm asking for some help.
In my .h I have declared:
NSArray *sortedContacts;
NSArray *rawContacts;
And then in .m:
-(void) buildContacts {
ABAddressBook *addressBook = [ABAddressBook sharedAddressBook];
NSArray *contacts = [addressBook people];
rawContacts=contacts;
NSArray *firstNames = [rawContacts valueForKey:#"First"];
NSArray *lastNames = [rawContacts valueForKey:#"Last"];
NSArray *organization = [rawContacts valueForKey:#"Organization"];
NSMutableArray *fullNames = [NSMutableArray array];
for(int i = 0; i < [firstNames count]; i++)
{
NSString *fullName = [NSString stringWithFormat:#"%# %# %#",
[firstNames objectAtIndex:i],
[lastNames objectAtIndex:i],
[organization objectAtIndex:i]];
[fullNames addObject:fullName];
}
NSMutableArray *fullList = [[NSMutableArray alloc]initWithArray:fullNames];
[fullList removeObjectIdenticalTo: #"<null>"];
sortedContacts = [fullList sortedArrayUsingSelector:#selector(compare:)];
NSLog(#"%#",sortedContacts);
}
I've tried so many things that I just can't see the forest for the trees anymore.

The text <null> is how the singleton instance of NSNull describes itself. That is, it's what -[NSNull description] returns.
In turn, these NSNull objects are getting into your firstNames, lastNames, and organization arrays because that's what Key-Value Coding does when you call -valueForKey: on an array and some of the elements return nil when that message is forwarded on to them with the same key. That is, calling [rawContacts valueForKey:#"First"] causes NSArray to call [element valueForKey:#"First"] for each element in rawContacts and to put the result in the array it builds. But, since an array can't contain nil, if one of those elements returns nil from [element valueForKey:#"First"], an NSNull object is added in its place.
Then, you are formatting the string fullName from the corresponding elements of firstNames, lastNames, and organization. You need to check if any of those elements are NSNull using if ([value isKindOfClass:[NSNull class]]) and handling that. For instance, you might just skip that record. Or you might combine the available fields and leave out any unavailable ones.
In any case, none of the elements of fullList will be #"<null>" because formatting values into #"%# %# %#" can never result in that string. (It might be #"<null> <null> <null>" or something like that, but never just #"<null>".)

A quick look at your code suggests you cannot get any empty strings added to your array, (a) you add elements using:
[fullNames addObject:fullName];
and fullName is created using:
[NSString stringWithFormat:#"%# %# %#" ...
so even if the %#'s get replaced by nothing you'll still have 2 spaces...
Maybe this is why all the things you've tried fail, if you're looking for empty strings you won't find them.
(Addendum: Question now says you're looking for #"<null>", you won't get that either for the same reason - there is at least two spaces in your string.)
The simple answer to removing invalid entries in fullNames is not to add them in the first place. You are adding elements in a loop (for), and conditional logic (e.g. if) inside the loop to determine whether you have something valid to add - however you define "something valid" - and only add an item to fullNames if so.
HTH

I'm not really familiar with the AddressBook framework, however this might be what's causing the confusion:
The values you collect in your arrays firstNames, lastNames and organization can be of type NSString or NSNull. You have to do any null-checking within the for-loop, before the fullName-string is constructed.
Remove this useless line:
[fullList removeObjectIdenticalTo: #"<null>"];
And replace the contents of your for-loop with the following code:
for(int i = 0; i < [firstNames count]; i++)
{
NSString *firstName = [firstNames objectAtIndex:i];
NSString *lastName = [lastNames objectAtIndex:i];
NSString *org = [organization objectAtIndex:i];
NSMutableArray *namesArray = [NSMutableArray array];
if ([firstName isKindOfClass:[NSString class]])
[namesArray addObject:firstName];
if ([lastName isKindOfClass:[NSString class]])
[namesArray addObject:lastName];
if ([org isKindOfClass:[NSString class]])
[namesArray addObject:org];
if (namesArray.count > 0)
[fullNames addObject:[namesArray componentsJoinedByString:#" "]];
}

Related

Having trouble taking an index of an array and making it an NSString

I get an array from a JSON and I parse it into an NSMutableArray (this part is correct and working). I now want to take that array and print the first object to a Label. Here is my code:
NSDictionary *title = [[dictionary objectForKey:#"title"] objectAtIndex:2];
arrayLabel = [title objectForKey:#"label"];
NSLog(#"arrayLabel = %#", arrayLabel); // Returns correct
//Here is where I need help
string = [arrayLabel objectAtIndex:1]; //I do not get the first label (App crashes)
NSLog(#"string = %#", string);
other things that I have already tried are as follows:
string = [NSString stringWithFormat:#"%#", [arrayImage objectAtIndex:1]];
and
string = [[NSString alloc] initWithFormat:#"%#", [arrayImage objectAtIndex:1]];
Any help is greatly appriciated!
EDIT: The app does not return a single value and crashes.
Your code doesn't match the structure of your JSON. In your comment on the deleted answer, you said you got an exception when sending objectAtIndex: to an NSString. In your case, arrayLabel isn't an array when you think it is.
If your JSON has an object, your code needs to treat it as an NSDictionary. Likewise for arrays and NSArray and strings and NSString.
In addition to whatever else was going on, you repeatedly refer to "first" but use the index 1. In most C-based programming languages (and others, as well) the convention is that indexes into arrays are 0-based. So, use index 0 to get the first element.

copy and autorelease in enumeration

I am trying to change the contents of an array while enumeration. To avoid the warning ,"the array was mutated while being enumerated" i made a copy of the array and done the autorelease like this-
int iKeyArrayCount=0;
for(NSString *keyEntity in [[keyArray copy]autorelease])
{
[keyArray replaceObjectAtIndex:iKeyArrayCount withObject:[keyEntity stringByReplacingOccurrencesOfString:#"\"" withString:kMPVTBlankString]];
[keyArray replaceObjectAtIndex:iKeyArrayCount withObject:[keyEntity stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]];
iKeyArrayCount++;
}
My confusion is regarding the enumeration over that copy of keyArray. While enumeration is it like , a copy of keyArray is formed each time the for loop executes? Or only one copy of that keyArray is formed during the whole process of enumeration.
In that code snippet [[keyArray copy] autorelease] is executed once only, and the result is used at the object being iterated.
In other words, "only one copy of that keyArray is formed during the whole process of enumeration" is correct.
It doesn't make sense to copy the whole array for this. You could also just use a regular for-loop instead of the enumeration:
for (NSInteger i = 0; i < [keyArray count]; i++)
{
NSString *keyEntity = [keyArray objectAtIndex:i];
NSString *newKeyEntity = [keyEntity stringByReplacingOccurrencesOfString:#"\\" withString:#" "];
newKeyEntity = [newKeyEntity stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
[keyArray replaceObjectAtIndex:i withObject:newKeyEntity];
}
Things may get dirty if you start adding/deleting objects in the loop, but as long as you just replace them this is just fine.
As you may have already noticed that stringByReplacingOccurrencesOfString:withString: is useless in your example, since you replace it in the next line once again.

How does one populate an NSMutable array of NSMutableSets?

I am using this code in a loop to populate an NSMutable Array of NSMutableSets (of NSString objects). The index of the NSSet is based on the length of the word.
// if set of this length not initialized yet, initialize set.
wordIndex = [NSString stringWithFormat:#"%d", currentWordLength];
if ([myWordArray objectForKey:wordIndex] == nil)
[myWordArray setObject:[[NSMutableSet alloc] initWithObjects:currentWord, nil] forKey:wordIndex];
else
[[myWordArray objectForKey:wordIndex] addObject:currentWord];
The final intention is to split up an array of words into an array of sets of words grouped by their lengths.
However, I see that [myWordArray count] is 0 after this. Why?
You are confusing the methods of NSMutableDictionary and NSMutableArray: In Objective-C arrays do not have keys but have indexes. If you change the class for myWordArray to NSMutableDicitionary it should work.
Try this, it looks very much like your logic, but (1) it uses NSNumbers as keys, which makes a little more sense, (2) handles the missing set condition more simply, but just adding the set, and (3) breaks up the source lines somewhat for easier debugging...
NSArray *inputStrings = // however these are initialized goes here
NSMutableDictionary *result = [NSMutableDictionary dictionary];
for (NSString *currentString in inputStrings) {
NSInteger currentWordLength = currentString.length;
wordIndex = [NSNumber numberWithInt:currentWordLength];
NSMutableSet *wordSet = [result objectForKey:wordIndex];
if (!wordSet) {
wordSet = [NSMutableSet set];
[result setObject:wordSet forKey:wordIndex];
}
[wordSet addObject:currentWord];
}
If you still have an empty dictionary after running this, it might be simpler to watch what's happening by stepping through it.

How do you enumerate through an array in Obj-C?

I have an array which has several objects (all of different classes) in it. But using enumeration doesn't work on it for some reason.
NSString *arrayString;
NSURL *arrayUrl;
NSProcessInfo *arrayPr;
NSDictionary *arrayDictionary;
NSMutableString *arrayMString;
NSMutableArray *objectArray = [NSMutableArray arrayWithObjects:arrayString,arrayUrl,arrayPr,arrayDictionary,arrayMString,nil];
for( NSString *item in objectArray ){
NSLog(#"Class name is: %#", [item className]);
}
I think it might be something to do with how the objects are been added to the array but i'm new to objective-c and not sure.
you aren't actually populating the array.
NSString *arrayString;
declares a variable, arrayString, of type NSString. it's not initialized (so it deserves to crash when you use the variable -- but may be 0 with some build settings).
so, to assign a variable:
NSString *arrayString = [NSString stringWithFormat:#"sksjdhf %f\n", 3.3];
arrayWithObjects adds objects from the (va list) argument until nil/null/0 is encountered.
you must set up the remainder of your variables/arguments correctly before using them.
this should work as you expect it to:
NSString * str = #"a string";
NSMutableArray *objectArray = [NSMutableArray arrayWithObjects:str, nil];
for (NSObject * item in objectArray) {
NSLog(#"Class name is: %#", [item className]);
}
In the for loop, use an id data type. The id data type is a general purpose data type that can be used to store a reference to any object.
For example:
for ( id item in objectArray ) {
NSLog(#"Class name is: %#", [item className]);
}
Yep, that's how you do it. If you're having trouble, it is not in the enumeration syntax itself.

Sorting NSStrings of Numbers

So I have an NSDictionary where the keys are years as NSString's and the value for each key is also an NSString which is sort of a description for the year. So for example, one key is "943 B.C.", another "1886". The problem I am encountering is that I want to sort them, naturally, in ascending order.
The thing is that the data source of these years is already in order, it's just that when I go ahead and call setValue:forKey the order is lost, naturally. I imagine figuring out a way to sort these NSString's might be a pain and instead I should look for a method of preserving the order at the insertion phase. What should I do? Should I instead make this an NSMutableArray in which every object is actually an NSDictionary consisting of the key being the year and the value being the description?
I guess I just answered my own question, but to avoid having wasted this time I'll leave this up in case anyone can recommend a better way of doing this.
Thanks!
EDIT: I went ahead with my own idea of NSMutableArray with NSDictionary entries to hold the key/value pairs. This is how I am accessing the information later on, hopefully I'm doing this correctly:
// parsedData is the NSMutableArray which holdes the NSDictionary entries
for (id entry in parsedData) {
NSString *year = [[entry allKeys] objectAtIndex:0];
NSString *text = [entry objectForKey:year];
NSLog(#"Year: %#, Text: %#", year, text);
}
Maintain a NSMutableArray to store the keys in order, in addition to the NSDictionary which holds all key-value pairs.
Here is a similar question.
You could either do it as an array of dictionaries, as you suggest, or as an array of strings where the strings are the keys to your original dictionary. The latter is probably a simpler way of going about it. NSDictionary does not, as I understand it, maintain any particular ordering of its keys, so attempting to sort the values there may be unwise.
I needed to solve a similar problem to sort strings of operating system names, such as "Ubuntu 10.04 (lucid)".
In my case, the string could have any value, so I sort by tokenizing and testing to see if a token is a number. I'm also accounting for a string like "8.04.2" being considered a number, so I have a nested level of tokenizing. Luckily, the nested loop is typically only one iteration.
This is from the upcoming OpenStack iPhone app.
- (NSComparisonResult)compare:(ComputeModel *)aComputeModel {
NSComparisonResult result = NSOrderedSame;
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
NSArray *tokensA = [self.name componentsSeparatedByString:#" "];
NSArray *tokensB = [aComputeModel.name componentsSeparatedByString:#" "];
for (int i = 0; (i < [tokensA count] || i < [tokensB count]) && result == NSOrderedSame; i++) {
NSString *tokenA = [tokensA objectAtIndex:i];
NSString *tokenB = [tokensB objectAtIndex:i];
// problem: 8.04.2 is not a number, so we need to tokenize again on .
NSArray *versionTokensA = [tokenA componentsSeparatedByString:#"."];
NSArray *versionTokensB = [tokenB componentsSeparatedByString:#"."];
for (int j = 0; (j < [versionTokensA count] || j < [versionTokensB count]) && result == NSOrderedSame; j++) {
NSString *versionTokenA = [versionTokensA objectAtIndex:j];
NSString *versionTokenB = [versionTokensB objectAtIndex:j];
NSNumber *numberA = [formatter numberFromString:versionTokenA];
NSNumber *numberB = [formatter numberFromString:versionTokenB];
if (numberA && numberB) {
result = [numberA compare:numberB];
} else {
result = [versionTokenA compare:versionTokenB];
}
}
}
[formatter release];
return result;
}