Objective-C, correctly separate columns in CSV by comma - objective-c

I have an Excel file that I exported as a CSV file.
Some of the data in the columns also have commas. CSV escapes these by putting the string in the column in quotes (""). However, when I try to parse it in Objective-C, the comma inside the string seperates the data, as if it were a new column.
Here's what I have:
self.csvData = [NSString stringWithContentsOfURL:file encoding:NSASCIIStringEncoding error:&error];
//This is what the data looks like:
//"123 Testing (Sesame Street, Testing)",Hello World,Foo,Bar
//Get rows
NSArray *lines = [self.csvData componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
//Get columns
NSArray *columns = [[lines objectAtIndex:0] componentsSeparatedByString:#","];
//Show columns
for (NSString *column in columns) {
NSLog(#"%#", column);
}
//Console shows this:
"123 Testing (Sesame Street
Testing)"
Hello World
Foo
Bar
Notice how "123 Testing (Sesame Street and Testing)" are output as separate columns. I need these to be one. Any ideas?

Any ideas?
Design an algorithm.
At the start of the input you have one of three possibilities:
You have a " - parse a quoted field
You have a , - handle an empty field
Otherwise - parse a non-quoted field
After parsing if there is any more input left then iterate (loop).
You might start with some variables:
NSUInteger position = 0; // current position
NSUInteger remaining = csvData.length; // left to parse
then enter your loop:
while(remaining > 0)
{
get the next character:
unichar nextChar = [csvData characterAtIndex:position];
Now check if that character is a ", , or something else. You can use an if or a switch.
Let's say it's a comma, then you want to find the position of the next comma. The NSString method rangeOfString:options:range will give you that. The last argument to this method is a range specifying in which part of the string to search for the comma. You can construct that range using NSMakeRange and values derived from position and remaining.
Once you have the next comma you need to extract the field, the substringWithRange: method can get you that.
Finally update position and remaining as required and you are ready for the next iteration.
You'll have to handle a few error cases - e.g. opening quote with no closing quote. Overall it is straight forward.
If you start down this path and it doesn't work ask a new question, showing your code and explaining where you got stuck.
HTH

Related

Multiple strings in one label

I have a label that needs to display two strings. At the moment I have this:
self.artistLabel.text = self.string1;
I want to be able to display string 1 and string 2. if possible, I also need to have an "#" symbol in between the two strings. I know it must be simple, I just can't figure it out.
You can do it like this:
self.artistLabel.text = [NSString
stringWithFormat:#"%# %#"
, self.string1
, self.string2
];
//Create a new string from both string1 and string2 with an "#" in the middle
NSString *artistLabelString = [NSString stringWithFormat:"%###%#", self.string1, self.string2];
//Set the label
self.artistLabel.text = artistLabelString;
Just as a BTW, the ## in the middle gives you a single #.
You can display only one string in one UILabel. However, that string can itself be a combination of multiple strings.
To combine strings, look into methods like stringByAppendingString:.

Optimise searching in an array, search by comparison of 2 strings in Objective C

I have a list of contacts retrieved from Address book stored inside a MutableArray contactList. Each contact is an object which has properties like "contactName, contactImage.... etc".
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{
//getAllContacts is a method which returns a Mutable array of Objects
self.contactList = [NSMutableArray arrayWithArray:[instance getAllContacts]];
//groupLetterToLoad could be "DEF"
for(int j=0; j<self.groupLetterToLoad.length;j++) {
//1st iteration D, 2nd iteration E and 3rd iteration F
NSString *testChar = [NSString stringWithFormat:#"%c",[self.groupLetterToLoad characterAtIndex:j]];
//check D,E,F with contact name property's first letter of the contact list array
for(int i=0;i<self.contactList.count;i++) {
NSString *firstChar =[[[self.contactList objectAtIndex:i] contactName] substringToIndex:1];
if([testChar isEqualToString: firstChar]) {
pos=i; //retrieve the index of the matched position
break;
}
}
if(pos!=-1) break;
}
});
Now this has two for loops (Time O(n^2)).. The disadvantage here is, if the groupLetterToLoad is "WXYZ", then comparison will start from W with A to W with Z.. How can I optimise it?
Ordering your array by contactName and performing a half interval search will reduce your complexity greatly if can avoid sorting every time you search (hint: keep [instance getAllContacts] sorted).
http://rosettacode.org/wiki/Binary_search#Objective-C - that's a starting point. you could replace the compare: with your first character comparison.
This isn't an algorithmic improvement, but the way you're handling characters is about the slowest way possible. If your group letters are really ASCII letters as you indicate, try this (I include the "if" in my answer because doing correct comparison of non-ASCII is really best left up to NSString):
1) Instead of using -substringToIndex to get the first character, use -characterAtIndex:0 and store a unichar
2) Instead of using +stringWithFormat:#"%c" to make a single character string, just use -characterAtIndex: and store it in a unichar
3) Instead of using -isEqualToString:, use == on the unichars
Unrelated, I'm pretty suspicious of the thread-safety of this. Are all those properties on self and instance you're accessing really not accessed on any other queue or thread?

Parsing text from one array into another array in Objective C

I created an array called NSArray citiesList from a text file separating each object by the "," at the end of the line. Here is what the raw data looks like from the text file.
City:San Jose|County:Santa Clara|Pop:945942,
City:San Francisco|County:San Francisco|Pop:805235,
City:Oakland|County:Alameda|Pop:390724,
City:Fremont|County:Alameda|Pop:214089,
City:Santa Rosa|County:Sonoma|Pop:167815,
The citiesList array is fine (I can see count the objects, see the data, etc.) Now I want to parse out the city and Pop: in each of the array objects. I assume that you create a for loop to run through the objects, so if I wanted to create a mutable array called cityNames to populate just the city names into this array I would use this kind of for loop:
SMutableArray *cityNames = [NSMutableArray array];
for (NSString *i in citiesList) {
[cityNames addObject:[???]];
}
My question is what is what query should I use to find just the City: San Francisco from the objects in my array?
You can continue to use componentsSeparatedByString to divide up the sections and key/value pairs. Or you can use an NSScanner to read through the string parsing out the key/value pairs. You could use rangeOfString to find the "|" and then extract a range. So many options.
Many good suggestions in the answers here in case you really want to construct an algorithm to parse the string.
As an alternative to that, you can also look at it as a problem of declaring the structure of the data and then just have the system do the parsing. For a case like yours, regular expressions will do that nicely. Whether you prefer to do it one way or the other is largely a question of taste and coding standards.
In your specific case (if the city name is all you need to extract from the string), then also notice that there is a bit of a shortcut available that will turn it into a one-line solution: Match the whole string, define a single capture group and substitute that one to make a new string:
NSString *city = [i stringByReplacingOccurrencesOfString: #".*City:(.*?)\\|.*"
withString: #"$1"
options: NSRegularExpressionSearch
range: NSMakeRange(0, row.length)];
The variable i is the same that you have defined in your for-loop, i.e. a string containing a string representing a line in your input file:
City:San Jose|County:Santa Clara|Pop:945942,
I have added the initial .* to make the pattern robust to future new fields added to the rows. You can remove it if you don't like it.
The $1 in the substitution string represents the first capture group, i.e. the parenthesis in the regex pattern. In this specific case, the substring containing the city name. Had there been more capture groups, they would have been named $2-$9. You can check the documentation on NSRegularExpression and NSString if you want to know more.
Regular expressions are a topic all of their own, not confined to the Cocoa, although all platforms use regex implementations with their own idiosyncrasies.
You want to use componentsSeparatedByString: as below. (These lines do no error checking)
NSArray *fields = [i componentsSeparatedByString:#"|"];
NSString *city = [[[fields objectAtIndex:0] componentsSeparatedByString:#":"] objectAtIndex:1];
NSString *county = [[[fields objectAtIndex:1] componentsSeparatedByString:#":"] objectAtIndex:1];
If you can drop the keys, and a couple delimiters like this:
San Jose|Santa Clara|945942
San Francisco|San Francisco|805235
Oakland|Alameda|390724
Fremont|Alameda|214089
Santa Rosa|Sonoma|167815
Then you can simplify the code (still no error checking):
NSArray *fields = [i componentsSeparatedByString:#"|"];
NSString *city = [fields objectAtIndex:0];
NSString *county = [fields objectAtIndex:1];
for (NSString *i in citiesList) {
// Divide each city into an array, where object 0 is the name, 1 is county, 2 is pop
NSArray *stringComponents = [i componentsSeparatedByString:#"|"];
// Remove "City:" from string and add the city name to the array
NSString *cityName = [[stringComponents objectAtIndex:0] stringByReplacingCharactersInRange:NSMakeRange(0, 5) withString:#""];
[cityNames addObject:cityName];
}

How to take out the first component of an array when it doesn't have any contents

I made a textview that is bulleted only. It works just like a bulleted list in word. Now I am making an array using this code to separate strings by a bullet point (\u2022)
//get the text inside the textView
NSString *textContents = myTextView.text;
//make the array
NSArray *bulletedArray = [textContents componentsSeparatedByString:#"\u2022"];
//print out array
NSLog(#"%#",bulletedArray);
It works perfectly with separating the text into components by bullet points but it keeps the first line that has nothing in it. So when it prints out it looks like this.
"",
"Here is my first statement\n\n",
"Here is my second statement.\n\n",
"This is my third statement. "
The very first component of the array is "" (nothing). Is there a way to avoid adding components that equal nil?
Thanks.
Sadly, this is the way the componentsSeparatedBy... methods of NSString work:
Adjacent occurrences of the separator characters produce empty strings in the result. Similarly, if the string begins or ends with separator characters, the first or last substring, respectively, is empty.
Since you know that the first element will always be empty, you can make a sub-array starting at element 1:
NSArray *bulletedArray = [textContents componentsSeparatedByString:#"\u2022"];
NSUInteger len = bulletedArray.count;
if (bulletedArray.count) {
bulletedArray = [bulletedArray subarrayWithRange:NSMakeRange(1, len-1)];
}
Alternatively, you can use substringFromIndex: to chop off the initial bullet character from the string before passing it to the componentsSeparatedByString: method:
NSArray *bulletedArray = [
[textContents substringFromIndex:[textContents rangeOfString:#"\u2022"].location+1]
componentsSeparatedByString:#"\u2022"];
[[NSMutableArray arrayWithArray:[textContents componentsSeparatedByString:#"\u2022"]] removeObjectIdenticalTo:#""];
that should do the trick
while your bulleted list has always a bullet on index 1, you can simply cut the first index out of the string:
//get the text inside the textView
if (myTextView.text.length > 1) {
NSString *textContents =[myTextView.text substringFromIndex:2];
//make the array
NSArray *bulletedArray = [textContents componentsSeparatedByString:#"\u2022"];
//print out array
NSLog(#"%#",bulletedArray);
}
of course you should avoid having an empty text, while this would cause an arrayOutOfBounds Exception.

Get longest word from a list of words

Is there a quick method that gets the largest word from an array of words?
NSMutableArray wordlist
Something like the following should do the trick:
NSString *longest = nil;
for(NSString *str in wordlist) {
if (longest == nil || [str length] > [longest length]) {
longest = str;
}
}
I'm not aware of any simpler method.
You could use something like this example to sort an Array (but instead of the 'quality' sort in the example use a length sort on your strings) and then the longest string will be either at the top or at the end (depending on your sorting).
I don't know any objective C myself, but my solution would be to keep an integer 'longest' and a string 'longestWord' and initialise it to 0 and "". Then loop over the list and check if the current word is longer then the 'longest' value. If it is, store the new length and the current word. At the end of the loop, you have the longest word stored in the 'longestWord' variable.
Hope this helps