How to Concatenate String in Objective-C (iPhone)? [duplicate] - iphone

This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
How do I concatenate strings in Objective-C?
Firstly, the platform is iPhone and label.text changes the label displayed. Consider this scenario:
I've an array of integers. And I want to display it on the screen.
Here's my take on it:
-(IBAction) updateText: (id)sender {
int a[2];
a[0]=1;
a[1]=2;
a[2]=3;
for (int i=0; i<=10;i++)
label.text = [NSString stringByAppendingString: [NSString stringWithFormat: #"%i", a[i]]];
}
As you can probably see, I'm pretty confused. Pls pls help me out :(

Try this:
NSMutableString* theString = [NSMutableString string];
for (int i=0; i<=10;i++){
[theString appendString:[NSString stringWithFormat:#"%i ",i]];
}
label.text = theString;

Since you're using a loop, do be somewhat careful with both Tom and Benjie's solutions. They each create an extra autoreleased object per iteration. For a small loop, that's fine, but if the size of the loop is unbounded or if the strings are large, this can lead to a very large memory spike and performance hit. Particularly on iPhone, this is exactly the kind of loop that can lead to surprising memory problems due to short-lived memory spikes.
The following solution has a smaller memory footprint (it's also slightly faster and takes less typing). Note the call to -appendFormat: rather than -appendString. This avoids creating a second string that will be thrown away. Remember that the final string has an extra space at the end that you may want to get rid of. You can fix that by either treating the first or last iteration differently, or by trimming the last space after the loop.
NSMutableString* theString = [NSMutableString string];
for (int i=0; i<=10;i++){
[theString appendFormat:#"%i ",i];
}
label.text = theString;
Don't forget [NSArray componentsJoinedByString:]. In this case you don't have an NSArray, but in the common cases where you do, this is probably the best way to get what you're looking for.

//NSArray *chunks
string = [chunks componentsJoinedByString: #","];

Another method without using NSMutableString:
NSString* theString = #"";
for (int i=0; i<=10;i++){
theString = [theString stringByAppendingFormat:#"%i ",i];
}
label.text = theString;
Here's a full implementation (correcting your ranges):
-(IBAction) updateText: (id)sender {
int a[3];
a[0]=1;
a[1]=2;
a[2]=3;
NSString *str = #"";
for (int i=0; i<3;i++)
str = [str stringByAppendingFormat:#"%i ",i];
label.text = str;
}
You could also do it like this (e.g. if you wanted a comma separated list):
-(IBAction) updateText: (id)sender {
int a[3];
a[0]=1;
a[1]=2;
a[2]=3;
NSMutableArray *arr = [NSMutableArray arrayWithCapacity:3];
for (int i=0; i<3;i++)
[arr addObject:[NSString stringWithFormat:#"%i",i]];
label.text = [arr componentsJoinedByString:#", "];
}

Related

Regex pattern and/or NSRegularExpression a bit too slow searching over very large file, can it be optimized?

In an iOS framework, I am searching through this 3.2 MB file for pronunciations: https://cmusphinx.svn.sourceforge.net/svnroot/cmusphinx/trunk/pocketsphinx/model/lm/en_US/cmu07a.dic
I am using NSRegularExpression to search for an arbitrary set of words that are given as an NSArray. The search is done through the contents of the large file as an NSString. I need to match any word that appears bracketed by a newline and a tab character, and then grab the whole line, for example if I have the word "monday" in my NSArray I want to match this line within the dictionary file:
monday M AH N D IY
This line starts with a newline, the string "monday" is followed by a tab character, and then the pronunciation follows. The entire line needs to be matched by the regex for its ultimate output. I also need to find alternate pronunciations of the words which are listed as follows:
monday(2) M AH N D EY
The alternative pronunciations always begin with (2) and can go as high as (5). So I also search for iterations of the word followed by parentheses containing a single number bracketed by a newline and a tab character.
I have a 100% working NSRegularExpression method as follows:
NSArray *array = [NSArray arrayWithObjects:#"friday",#"monday",#"saturday",#"sunday", #"thursday",#"tuesday",#"wednesday",nil]; // This array could contain any arbitrary words but they will always be in alphabetical order by the time they get here.
// Use this string to build up the pattern.
NSMutableString *mutablePatternString = [[NSMutableString alloc]initWithString:#"^("];
int firstRound = 0;
for(NSString *word in array) {
if(firstRound == 0) { // this is the first round
firstRound++;
} else { // After the first iteration we need an OR operator first.
[mutablePatternString appendString:[NSString stringWithFormat:#"|"]];
}
[mutablePatternString appendString:[NSString stringWithFormat:#"(%#(\\(.\\)|))",word]];
}
[mutablePatternString appendString:#")\\t.*$"];
// This results in this regex pattern:
// ^((change(\(.\)|))|(friday(\(.\)|))|(monday(\(.\)|))|(saturday(\(.\)|))|(sunday(\(.\)|))|(thursday(\(.\)|))|(tuesday(\(.\)|))|(wednesday(\(.\)|)))\t.*$
NSRegularExpression * regularExpression = [NSRegularExpression regularExpressionWithPattern:mutablePatternString
options:NSRegularExpressionAnchorsMatchLines
error:nil];
int rangeLocation = 0;
int rangeLength = [string length];
NSMutableArray * matches = [NSMutableArray array];
[regularExpression enumerateMatchesInString:string
options:0
range:NSMakeRange(rangeLocation, rangeLength)
usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop){
[matches addObject:[string substringWithRange:result.range]];
}];
[mutablePatternString release];
// matches array is returned to the caller.
My issue is that given the big text file, it isn't really fast enough on the iPhone. 8 words take 1.3 seconds on an iPhone 4, which is too long for the application. Given the following known factors:
• The 3.2 MB text file has the words to match listed in alphabetical order
• The array of arbitrary words to look up are always in alphabetical order when they get to this method
• Alternate pronunciations start with (2) in parens after the word, not (1)
• If there is no (2) there won't be a (3), (4) or more
• The presence of one alternative pronunciation is rare, occurring maybe 1 time in 8 on average. Further alternate pronunciations are even rarer.
Can this method be optimized, either by improving the regex or some aspect of the Objective-C? I'm assuming that NSRegularExpression is already optimized enough that it isn't going to be worthwhile trying to do it with a different Objective-C library or in C, but if I'm wrong here let me know. Otherwise, very grateful for any suggestions on improving the performance. I am hoping to make this generalized to any pronunciation file so I'm trying to stay away from solutions like calculating the alphabetical ranges ahead of time to do more constrained searches.
****EDIT****
Here are the timings on the iPhone 4 for all of the search-related answers given by August 16th 2012:
dasblinkenlight's create NSDictionary approach https://stackoverflow.com/a/11958852/119717: 5.259676 seconds
Ωmega's fastest regex at https://stackoverflow.com/a/11957535/119717: 0.609593 seconds
dasblinkenlight's multiple NSRegularExpression approach at https://stackoverflow.com/a/11969602/119717: 1.255130 seconds
my first hybrid approach at https://stackoverflow.com/a/11970549/119717: 0.372215 seconds
my second hybrid approach at https://stackoverflow.com/a/11970549/119717: 0.337549 seconds
The best time so far is the second version of my answer. I can't mark any of the answers best, since all of the search-related answers informed the approach that I took in my version so they are all very helpful and mine is just based on the others. I learned a lot and my method ended up a quarter of the original time so this was enormously helpful, thank you dasblinkenlight and Ωmega for talking it through with me.
Since you are putting the entire file into memory anyway, you might as well represent it as a structure that is easy to search:
Create a mutable NSDictionary words, with NSString keys and NSMutableArray values
Read the file into memory
Go through the string representing the file line-by-line
For each line, separate out the word part by searching for a '(' or a '\t' character
Get a sub-string for the word (from zero to the index of the '(' or '\t' minus one); this is your key.
Check if the words contains your key; if it does not, add new NSMutableArray
Add line to the NSMutableArray that you found/created at the specific key
Once your are finished, throw away the original string representing the file.
With this structure in hand, you should be able to do your searches in time that no regex engine would be able to match, because you replaced a full-text scan, which is linear, with a hash look-up, which is constant-time.
** EDIT: ** I checked the relative speed of this solution vs. regex, it is about 60 times faster on a simulator. This is not at all surprising, because the odds are stacked heavily against the regex-based solution.
Reading the file:
NSBundle *bdl = [NSBundle bundleWithIdentifier:#"com.poof-poof.TestAnim"];
NSString *path = [NSString stringWithFormat:#"%#/words_pron.dic", [bdl bundlePath]];
data = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
NSMutableDictionary *tmp = [NSMutableDictionary dictionary];
NSUInteger pos = 0;
NSMutableCharacterSet *terminator = [NSMutableCharacterSet characterSetWithCharactersInString:#"\t("];
while (pos != data.length) {
NSRange remaining = NSMakeRange(pos, data.length-pos);
NSRange next = [data
rangeOfCharacterFromSet:[NSCharacterSet newlineCharacterSet]
options:NSLiteralSearch
range:remaining
];
if (next.location != NSNotFound) {
next.length = next.location - pos;
next.location = pos;
} else {
next = remaining;
}
pos += (next.length+1);
NSString *line = [data substringWithRange:next];
NSRange keyRange = [line rangeOfCharacterFromSet:terminator];
keyRange.length = keyRange.location;
keyRange.location = 0;
NSString *key = [line substringWithRange:keyRange];
NSMutableArray *array = [tmp objectForKey:key];
if (!array) {
array = [NSMutableArray array];
[tmp setObject:array forKey:key];
}
[array addObject:line];
}
dict = tmp; // dict is your NSMutableDictionary ivar
Searching:
NSArray *keys = [NSArray arrayWithObjects:#"sunday", #"monday", #"tuesday", #"wednesday", #"thursday", #"friday", #"saturday", nil];
NSMutableArray *all = [NSMutableArray array];
NSLog(#"Starting...");
for (NSString *key in keys) {
for (NSString *s in [dict objectForKey:key]) {
[all addObject:s];
}
}
NSLog(#"Done! %u", all.count);
Try this one:
^(?:change|monday|tuesday|wednesday|thursday|friday|saturday|sunday)(?:\([2-5]\))?\t.*$
and also this one (using positive lookahead with list of possible first letters):
^(?=[cmtwfs])(?:change|monday|tuesday|wednesday|thursday|friday|saturday|sunday)(?:\([2-5]\))?\t.*$
and at the end, a version with some optimization:
^(?=[cmtwfs])(?:change|monday|t(?:uesday|hursday)|wednesday|friday|s(?:aturday|unday))(?:\([2-5]\))?\t.*$
Here is my hybrid approach of dasblinkenlight's and Ωmega's answers, which I thought I should add as an answer as well at this point. It uses dasblinkenlight's method of doing a forward search through the string and then performs the full regex on a small range in the event of a hit, so it exploits the fact that the dictionary and words to look up are both in alphabetical order and benefits from the optimized regex. Wish I had two best answer checks to give out! This gives the correct results and takes about half of the time of the pure regex approach on the Simulator (I have to test on the device later to see what the time comparison is on the iPhone 4 which is the reference device):
NSMutableArray *mutableArrayOfWordsToMatch = [[NSMutableArray alloc] initWithArray:array];
NSMutableArray *mutableArrayOfUnfoundWords = [[NSMutableArray alloc] init]; // I also need to know the unfound words.
NSUInteger pos = 0;
NSMutableString *mutablePatternString = [[NSMutableString alloc]initWithString:#"^(?:"];
int firstRound = 0;
for(NSString *word in array) {
if(firstRound == 0) { // this is the first round
firstRound++;
} else { // this is all later rounds
[mutablePatternString appendString:[NSString stringWithFormat:#"|"]];
}
[mutablePatternString appendString:[NSString stringWithFormat:#"%#",word]];
}
[mutablePatternString appendString:#")(?:\\([2-5]\\))?\t.*$"];
// This creates a string that reads "^(?:change|friday|model|monday|quidnunc|saturday|sunday|thursday|tuesday|wednesday)(?:\([2-5]\))?\t.*$"
// We don't want to instantiate the NSRegularExpression in the loop so let's use a pattern that matches everything we're interested in.
NSRegularExpression * regularExpression = [NSRegularExpression regularExpressionWithPattern:mutablePatternString
options:NSRegularExpressionAnchorsMatchLines
error:nil];
NSMutableArray * matches = [NSMutableArray array];
while (pos != data.length) {
if([mutableArrayOfWordsToMatch count] <= 0) { // If we're at the top of the loop without any more words, stop.
break;
}
NSRange remaining = NSMakeRange(pos, data.length-pos);
NSRange next = [data
rangeOfString:[NSString stringWithFormat:#"\n%#\t",[mutableArrayOfWordsToMatch objectAtIndex:0]]
options:NSLiteralSearch
range:remaining
]; // Just search for the first pronunciation.
if (next.location != NSNotFound) {
// If we find the first pronunciation, run the whole regex on a range of {position, 500} only.
int rangeLocation = next.location;
int searchPadding = 500;
int rangeLength = searchPadding;
if(data.length - next.location < searchPadding) { // Only use 500 if there is 500 more length in the data.
rangeLength = data.length - next.location;
}
[regularExpression enumerateMatchesInString:data
options:0
range:NSMakeRange(rangeLocation, rangeLength)
usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop){
[matches addObject:[data substringWithRange:result.range]];
}]; // Grab all the hits at once.
next.length = next.location - pos;
next.location = pos;
[mutableArrayOfWordsToMatch removeObjectAtIndex:0]; // Remove the word.
pos += (next.length+1);
} else { // No hits.
[mutableArrayOfUnfoundWords addObject:[mutableArrayOfWordsToMatch objectAtIndex:0]]; // Add to unfound words.
[mutableArrayOfWordsToMatch removeObjectAtIndex:0]; // Remove from the word list.
}
}
[mutablePatternString release];
[mutableArrayOfUnfoundWords release];
[mutableArrayOfWordsToMatch release];
// return matches to caller
EDIT: here is another version which uses no regex and shaves a little bit more time off of the method:
NSMutableArray *mutableArrayOfWordsToMatch = [[NSMutableArray alloc] initWithArray:array];
NSMutableArray *mutableArrayOfUnfoundWords = [[NSMutableArray alloc] init]; // I also need to know the unfound words.
NSUInteger pos = 0;
NSMutableArray * matches = [NSMutableArray array];
while (pos != data.length) {
if([mutableArrayOfWordsToMatch count] <= 0) { // If we're at the top of the loop without any more words, stop.
break;
}
NSRange remaining = NSMakeRange(pos, data.length-pos);
NSRange next = [data
rangeOfString:[NSString stringWithFormat:#"\n%#\t",[mutableArrayOfWordsToMatch objectAtIndex:0]]
options:NSLiteralSearch
range:remaining
]; // Just search for the first pronunciation.
if (next.location != NSNotFound) {
NSRange lineRange = [data lineRangeForRange:NSMakeRange(next.location+1, next.length)];
[matches addObject:[data substringWithRange:NSMakeRange(lineRange.location, lineRange.length-1)]]; // Grab the whole line of the hit.
int rangeLocation = next.location;
int rangeLength = 750;
if(data.length - next.location < rangeLength) { // Only use the searchPadding if there is that much room left in the string.
rangeLength = data.length - next.location;
}
rangeLength = rangeLength/5;
int newlocation = rangeLocation;
for(int i = 2;i < 6; i++) { // We really only need to do this from 2-5.
NSRange morematches = [data
rangeOfString:[NSString stringWithFormat:#"\n%#(%d",[mutableArrayOfWordsToMatch objectAtIndex:0],i]
options:NSLiteralSearch
range:NSMakeRange(newlocation, rangeLength)
];
if(morematches.location != NSNotFound) {
NSRange moreMatchesLineRange = [data lineRangeForRange:NSMakeRange(morematches.location+1, morematches.length)]; // Plus one because I don't actually want the line break at the beginning.
[matches addObject:[data substringWithRange:NSMakeRange(moreMatchesLineRange.location, moreMatchesLineRange.length-1)]]; // Minus one because I don't actually want the line break at the end.
newlocation = morematches.location;
} else {
break;
}
}
next.length = next.location - pos;
next.location = pos;
[mutableArrayOfWordsToMatch removeObjectAtIndex:0]; // Remove the word.
pos += (next.length+1);
} else { // No hits.
[mutableArrayOfUnfoundWords addObject:[mutableArrayOfWordsToMatch objectAtIndex:0]]; // Add to unfound words.
[mutableArrayOfWordsToMatch removeObjectAtIndex:0]; // Remove from the word list.
}
}
[mutableArrayOfUnfoundWords release];
[mutableArrayOfWordsToMatch release];
Looking at the dictionary file you provided, I'd say that a reasonable strategy could be reading in the data and putting it into any sort of persistent data store.
Read through the file and create objects for each unique word, with n strings of pronunciations (where n is the number of unique pronunciations). The dictionary is already in alphabetical order, so if you parsed it in the order that you're reading it you'd end up with an alphabetical list.
Then you can do a binary search on the data - even with a HUGE number of objects a binary search will find what you're looking for very quickly (assuming alphabetical order).
You could probably even keep the whole thing in memory if you need lightning-fast performance.

Finding element in array

I have the follow code:
NSArray *myArray = [NSArray arrayWithObjects: #"e", #"è", #"é",#"i","ò",nil];
NSString *string = #"simpleè";
NSMutablestring *newString;
for(i=0>;i< [string length]; i++){
if([stringa characterAtIndex:i] is in Array){
[newString appendFormat:#"%c", [string characterAtIndex:i]];
}
}
How make finding if single char of string stay in the array?
Example of result:
newString= #"ieè";
I think you want to apply rangeOfCharacterFromSet:options:range: repeatedly. You'll have to create a NSCharacterSet from the characters in your array somehow.
Added
Though it probably would be just as simple to just loop through the string with characterAtIndex and compare each char (in an inner loop) to the chars in your array (which you could extract into a unichar array or put into a single NSString to make easier to access).
Umm... if you want to check what the values are you can use NSLog
NSLog"%f", myFloat;
So you can use this to check your array... Which is what I think you are asking for, but the grammar in your question isn't very good. Please provide more details and better grammar.
You should check the length of your string and then match your string characters with the array and if found append that character in a new string.
NSString *mstr = #"asdf";
NSString *b = [ mstr characterAtIndex:0];
Hope it helps.......
You'll want to create an NSCharacterSet with the characters in the string and then ask each string in the array for its rangeOfCharacterFromSet:. If you find one where a range was actually found, then a character from the string is in the array. If not, then none of them are.
This might seem a bit roundabout, but Unicode makes looking at strings as just a series of "chars" rather unreliable, and you'll want to let your libraries do as much of the heavy lifting for you as they can.
There are better ways to do this, but this is what I think you're trying to do:
NSMutableString* result= [NSMutableString stringWithString:#""];
for( int i= 0; i < [string length]; ++i ) {
NSString* c= [NSString stringWithFormat:#"%C", [string characterAtIndex:i]];
if( [myArray containsObject:c] )
[result appendString:c];
}

Any Way To Separate Text In UITextField?

I have a paragraph (4 sentences) of text in an .plist array that loads into a UITextView.
By default, it presents the text how it is, as one big lump of text in a paragraph. I want to know if it is possible to split this up?
Such as Line 1: sdfafasfsafsa, then line 2: asfdsafs, line 3: adfsfsdfsdfa, etc.
Is there a way I can search for a . and then separate the lines accordingly? I would just edit the plist manually but there are hundreds of entries so it isn't easy to do.
NSString* tidiedString = [sourceString stringByReplacingOccurrencesOfString:#"." withString:#"\n"];
Update: OK, so more detail is coming through. You could use a regular expression - but if you're not familiar, the learning curve is a bit steep. Otherwise, as with other answers, crank through the list. You need to take care of whitespaces, empty lines etc. The following snippet isn't pretty, but will do the job.
NSString* sourceString = #"Hyperlinks can be great. They can also dilute your focus and tempt you into putting off what you most want to do. Here I chose to place links at the foot of the page to help you to make an active choice as to whether to surf or refocus your attention elsewhere.";
NSArray* arrayOfStrings = [sourceString componentsSeparatedByString:#"."];
NSMutableString* superString = [NSMutableString stringWithString:#""];
int lineCount = 1;
for (NSString* string in arrayOfStrings)
{
if ([string length] < 1) continue;
[superString appendFormat:#"Line %d: %#.\n", lineCount++, [string stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]]];
}
[superString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
[[self userEntry] setText:superString];
NSArray *array = [sourceString componentsSeparatedByString:#"."];
NSMutableString *resultString= [[NSMutableString alloc] init];
int linecount=1;
for(NSString *lines in array)
{
[resultString appendString:[NSString stringWithFormat:#"Line%i:%#\n",linecount++,lines]];
}
NSLog(#"resultString:%#",resultString);
this may help..!!
Try making a loop that runs through the array and that adds every line to the UITextView plus #"\n".
So something like...:
NSString *curText = txtView.text;
NSString *lineBreak = #"\n";
txtView.text=[NSString stringWithFormat:#"%# + %#", curText, lineBreak];
Or just replace the dots by #"\n".

store each letter of NSString as object of NSMutableArray

I have an NSString of integer values. I need to add each one of the integers as a separate object in an NSMutableArray.
I tried characterAtIndex: but I keep getting errors…
P.s. I've solved over 30 problems thank's to stackoverflow's search, but didn't find any information on this problem.
NSMutableArray *results = [NSMutableArray array];
for (int i = 0; i < [string length]; i++)
{
NSString *substr = [string substringWithRange:NSMakeRange(i,1)];
[results addObject:[NSNumber numberWithInt:[substr intValue]];
}
NSLog(#" %# separated into: %#", string, results);
Consider looking at componentsSeparatedByString: or componentsSeparatedByCharactersInSet: for your purpose. Both methods are available on NSString.
If your integers are more than one digit or are space-separated, consider alternately using an NSScanner - you can read instances of NSInteger off the string one by one, wrap them in NSNumbers, and stick them in your array.

How to include a C array in -description

I'm trying to include the elements of an array in the NSString that's returned by the -description method in my class. No clue how to do this in Objective-C...in Java there's string concatenation or StringBuilder, what's the equivalent in Obj-C?
TIA..
Just use NSArray's componentsJoinedByString: method with whatever you want between them as the argument.
NSString *elementsSquishedTogether = [myArray componentsJoinedByString:#""];
NSString *connectedByACommaAndSpace = [myArray componentsJoinedByString:#", "];
If you have a C array, you can turn it into an NSArray with NSArray *converted = [NSArray arrayWithObjects:yourCArray count:yourArrayCount].
The title of your thread talks about C arrays, so here's a modification of jsumners' answer that will deal wiith C arrays.
myArray is assumed to be an ivar declared thusly:
int* myArray;
storage for myArray is assumed to be malloc'd at some point and the size of it is in an ivar declared:
int myArraySize;
The code for description goes something like
- (NSString *)description
{
NSMutableString *returnString = [[[NSMutableString alloc] init] autorelease];
for (int i = 0 ; i < myArraySize ; i++)
{
if (i > 0)
{
[returnString appendString: #", "];
}
[returnString appendFormat: #"%d", myArray[i]];
}
return [NSString stringWithFormat: #"[%#]", returnString];
}
There are variations. The above version formats the string with bracket delimiters and commas between elements. Also, it returns an NSString instead of an NSMutableString which is not a big deal, but I feel that if you say you are returning an immutable object, you probably should.
The following could should "build" a string representation of your array. Notice that it is using the -description method of the objects in the array. If you want something different you will have to make the necessary change.
- (NSString *)description: (id) myArr {
NSMutableString *returnString = [[[NSMutableString alloc] init] autorelease];
for (int i = 0, j = [myaArr count]; i < j; i++) {
[returnString appendString: [[myArr objectAtIndex: i] description]];
}
return [NSString stringWithString: returnString];
}
Edit:
As JeremyP said, I answered this using Objective-C arrays. I guess I just forgot the question when I started writing my code. I'm going to leave my answer as an alternative way to do it, though. I've also fixed the return string type from a mutable string to an immutable string (as it should be).