Problem with NSString and NSRange - objective-c

I'm having a rather annoying problem here. See, I'm trying to break up a string that I get into individual characters and symbols. The string is always in the form of an equation, like "3x+4" or "x/7+5". I need to separate the string into an array of individual strings. For example, if I had the first equation, I would want to have an NSMutableArray that has "3", "x", "+", and "4". Here is the section of code that I use:
NSMutableArray* list = [[NSMutableArray alloc] initWithCapacity:10];
for (int i = 0; i < [self.equationToGuess length]; i++) {
NSRange range = {i, i};
NSString* string= [[NSString alloc] initWithString:[self.equationToGuess substringWithRange:range]];
[list addObject:string];
}
I've made sure to check if self.equationToGuess always contains an equation using the debugger, and it does. list is also able to get some of the objects, but the problem is that it just puts the last two characters in one shelf on the list. So if I have that "3x+4" equation, this chunk of code puts "3", "x", and "+4" into the code, and then it crashes because it goes beyond the length of the string. Does anyone know how to fix this?

The two values in NSRange are not the starting and ending index. Rather, the first is the starting index and the second is the length of the range. So instead you want your range to be
NSRange range = NSMakeRange(i, 1);

Let's do this with a bit more panache:
NSInteger numberOfCharacters = [self.equationToGuess length];
NSMutableArray *characterArray = [[NSMutableArray alloc] initWithCapacity:numberOfCharacters];
for (NSUInteger idx = 0; idx < numberOfCharacters; idx++) {
NSRange range = NSMakeRange(idx, 1);
[characterArray addObject:[self.equationToGuess substringWithRange:range]];
}
Edit
After a hearty helping of humble pie - this is still not the best way to do it: if your equation has multi-digit coefficients, they will be split up. Have you considered using NSScanner to split the string up instead?

you could also use an alternative solution by getting characters from you string , for that you will have to use the below function of NSString.
- (unichar)characterAtIndex:(NSUInteger)index
.
NSMutableArray* list = [[NSMutableArray alloc] initWithCapacity:10];
NSString* string;
for (int i = 0; i < [self.equationToGuess length]; i++)
{
string = [[NSString alloc] initWithFormat:#"%c",[self.equationToGuess characterAtIndex:i]];
[list addObject:string];
[string release];
string = nil ;
}

NSInteger numberOfCharacters = [self.equationToGuess length];
NSMutableArray *characterArray = [[NSMutableArray alloc] initWithCapacity:numberOfCharacters];
for (NSUInteger idx = 0; idx < numberOfCharacters; idx++) {
[characterArray addObject:[NSString stringWithFormat:#"%c", [self.equationToGuess characterAtIndex:idx]]];
}

Related

Uppercase random characters in a NSString

I'm trying to figure out the best approach to a problem. I have an essentially random alphanumeric string that I'm generating on the fly:
NSString *string = #"e04325ca24cf20ac6bd6ebf73c376b20ac57192dad83b22602264e92dac076611b51142ae12d2d92022eb2c77f";
You can see that there are no special characters, just numbers and letters, and all the letters are lowercase. Changing all the letters in this string to uppercase is easy:
[string capitalizedString];
The hard part is that I want to capitalize random characters in this string, not all of them. For example, this could be the output on one execution:
E04325cA24CF20ac6bD6eBF73C376b20Ac57192DAD83b22602264e92daC076611b51142AE12D2D92022Eb2C77F
This could be the output on another, since it's random:
e04325ca24cf20aC6bd6eBF73C376B20Ac57192DAd83b22602264E92dAC076611B51142AE12D2d92022EB2c77f
In case it makes this easier, let's say I have two variables as well:
int charsToUppercase = 12;//hardcoded value for how many characters to uppercase here
int totalChars = 90;//total string length
In this instance it would mean that 12 random characters out of the 90 in this string would be uppercased. What I've figured out so far is that I can loop through each char in the string relatively easily:
NSUInteger len = [string length];
unichar buffer[len+1];
[string getCharacters:buffer range:NSMakeRange(0, len)];
NSLog(#"loop through each char");
for(int i = 0; i < len; i++) {
NSLog(#"%C", buffer[i]);
}
Still stuck with selecting random chars in this loop to uppercase, so not all are uppercased. I'm guessing a condition in the for loop could do the trick well, given that it's random enough.
Here's one way, not particularly concerned with efficiency, but not silly efficiency-wise either: create an array characters in the original string, building an index of which ones are letters along the way...
NSString *string = #"e04325ca24cf20ac6bd6ebf73c376b20ac57192dad83b22602264e92dac076611b51142ae12d2d92022eb2c77f";
NSMutableArray *chars = [#[] mutableCopy];
NSMutableArray *letterIndexes = [#[] mutableCopy];
for (int i=0; i<string.length; i++) {
unichar ch = [string characterAtIndex:i];
// add each char as a string to a chars collection
[chars addObject:[NSString stringWithFormat:#"%c", ch]];
// record the index of letters
if ([[NSCharacterSet letterCharacterSet] characterIsMember:ch]) {
[letterIndexes addObject:#(i)];
}
}
Now, select randomly from the letterIndexes (removing them as we go) to determine which letters shall be upper case. Convert the member of the chars array at that index to uppercase...
int charsToUppercase = 12;
for (int i=0; i<charsToUppercase && letterIndexes.count; i++) {
NSInteger randomLetterIndex = arc4random_uniform((u_int32_t)(letterIndexes.count));
NSInteger indexToUpdate = [letterIndexes[randomLetterIndex] intValue];
[letterIndexes removeObjectAtIndex:randomLetterIndex];
[chars replaceObjectAtIndex:indexToUpdate withObject:[chars[indexToUpdate] uppercaseString]];
}
Notice the && check on letterIndexes.count. This guards against the condition where charsToUppercase exceeds the number of chars. The upper bound of conversions to uppercase is all of the letters in the original string.
Now all that's left is to join the chars array into a string...
NSString *result = [chars componentsJoinedByString:#""];
NSLog(#"%#", result);
EDIT Looking discussion in OP comments, you could, instead of acharsToUppercase input parameter, be given a probability of uppercase change as an input. That would compress this idea into a single loop with a little less data transformation...
NSString *string = #"e04325ca24cf20ac6bd6ebf73c376b20ac57192dad83b22602264e92dac076611b51142ae12d2d92022eb2c77f";
float upperCaseProbability = 0.5;
NSMutableString *result = [#"" mutableCopy];
for (int i=0; i<string.length; i++) {
NSString *chString = [string substringWithRange:NSMakeRange(i, 1)];
BOOL toUppercase = arc4random_uniform(1000) / 1000.0 < upperCaseProbability;
if (toUppercase) {
chString = [chString uppercaseString];
}
[result appendString:chString];
}
NSLog(#"%#", result);
However this assumes a given uppercase probability for any character, not any letter, so it won't result in a predetermined number of letters changing case.

Parsing NSMutableString By Number Of Instances

I have an NSMutableString that contains a lot of data. A short example of it is:
MP3: name length album
MP3: name length album
MP3: name length album
MP3: name length album
Now, what I'm trying to accomplish is to parse the string to contain only the first 3 MP3 data sets. So once the fourth instance of "MP3:" is found, stop parsing.
I've tried multiple thing to accomplish this, but I've been staring at it too long and am starting to go goofy. If it was an array it would be simple but unfortunately it's a string. Does anyone know how to accomplish the logic behind this?
To add more clarity: I would still like the first the MP3's to appear. They will be different every time so I can't do a subStringToIndex.
To do this efficiently, assuming the input string is very long (containing thousands of lines after the first three), you need to avoid using componentsSeparatedByString:.
Instead, find the first three newlines in the string, by using rangeOfString: repeatedly.
NSString *input = #"MP3: line1\nMP3: line2\nMP3: line3\nMP3: line4\nMP3: line5\n";
NSUInteger lineStartLocation = 0;
NSUInteger inputLength = input.length;
for (int i = 0; i < 3; ++i) {
NSRange searchRange = NSMakeRange(lineStartLocation, inputLength - lineStartLocation);
NSRange newlineRange = [input rangeOfString:#"\n" options:0 range:searchRange];
if (newlineRange.location == NSNotFound) {
// Not enough lines in input!
break;
} else {
lineStartLocation = newlineRange.location + 1;
}
}
NSString *top3 = [input substringToIndex:lineStartLocation];
NSLog(#"top3 = %#", top3);
NSArray *lines = [data componentsSeparatedByString:#"\n"];
for (NSUInteger i = 0, count = 0; i < lines.count && count < 3; i++) {
NSString *line = lines[i];
NSRange range = [line rangeOfString:#"MP3:"];
if (range.location == 0 && range.length == #"MP3:".length) {
// parsing
count++;
}
}
NSString * stringToParse = #"MP3: hola MP3: pfff MP3: cosas MP3: hello";
NSArray * arrayStringParsed = [stringToParse componentsSeparatedByString:#"MP3:"];
And the results is and array:
<__NSArrayM 0x15fb2f80>(
,
hola ,
pfff ,
cosas ,
hello
)
First element doesn't have anything but the other 4 are parsed and you can work with them.

divide string into characters

I have an NSString *titleName which changes according to an if statement. So the length (number of characters) in the string changes. I would like to divide titleName into a MutableArray of separate strings consisting of its individual characters. I would then like to use these separate strings as the text in different UILabels. I am not sure as how to go about this.
Through some research I have tried to create the NSMutable array like this
NSMutableArray *letterArray = substringWithRange:((i = 0);i<[titleName2 length];i++));
but this gives me an error Use of undeclared identifier 'substringWithRange.
Can someone help me.
I decided to use componentsSeparatedByString instead and just created my various strings with a , between each letter. Thanks for anybody's thoughts though.
The code you pasted is not valid objective-C.
To keep the same algorithm you should write something like :
NSMutableArray *letterArray = [NSMutableArray array];
NSUInteger length = [titleName2 length];
for (NSUInteger i = 0; i < length; ++i) {
[letterArray addObject:[titleName2 substringWithRange:NSMakeRange(i, 1)]];
}
It's probably much "cheaper" to hold a C-array of unichar characters that make-up the string. It will also be quicker to create:
NSString *input = #"How now brown cow?";
unichar chars[[input length]];
for (NSInteger i = 0; i < [input length]; i++)
chars[i] = [input characterAtIndex:i];
Alternatively you could use malloc() to create the C-array:
NSString *input = #"How now brown cow?";
unichar *chars = (unichar *)malloc([input length]);
for (NSInteger i = 0; i < [input length]; i++)
chars[i] = [input characterAtIndex:i];
and then use free(), later, to, err, free the memory:
free(chars);
Cheaper still, would be to not split-up the string at all...
Try this below code
NSMutableArray *letterArray = [NSMutableArray array];
for (int i = 0;i<[titleName2 length];i++)
{
[letterArray addObject: [titleName2 substringWithRange:NSMakeRange(i,1)]];
}
DLog(#"%#", letterArray);
Other option to get characters of string
NSMutableArray *letterArray = [NSMutableArray array];
for (int i=0; i < [titleName2 length]; i++)
{
[letterArray addObject:[NSString stringWithFormat:#"%c", [titleName2 characterAtIndex:i]]];
}
DLog(#"characters - %#", letterArray);

Is there any method that can take "one" and return "1"?

I've looked at NSNumberFormatter, but that hasn't worked, so is there a way of parsing written numbers and turning them in to actual numbers?
Something like this would work (for positive whole numbers anyway). This is just a starting point, you would have to check to see that the words were correct and maybe ignore capitalization to make it more robust:
[self parseNumberWords:#"five two three"];
-(NSInteger)parseNumberWords:(NSString *)input {
NSArray *wordArray = [NSArray arrayWithObjects:#"zero",#"one",#"two",#"three",#"four",#"five",#"six",#"seven",#"eight",#"nine", nil];
NSArray *words = [input componentsSeparatedByString:#" "];
NSInteger num = 0;
NSInteger j =0;
for (NSInteger i = [words count]; i>0 ;i--) {
num = num + [wordArray indexOfObject:[words objectAtIndex:i-1]] * pow(10, j);
j = j+1;
}
NSLog(#"%ld",num);
return num;
}
NSNumberFormatter will get you some of the way there, via NSNumberFormatterSpellOutStyle. The basic formatting that NSNumber does will finish it off.
NSNumberFormatter * nf = [[NSNumberFormatter alloc] init];
[nf setNumberStyle:NSNumberFormatterSpellOutStyle];
NSString * numberWordString = #"three one two";
NSMutableString * digitString = [[NSMutableString alloc] init];
// Break up the input string at spaces and iterate over the result
for(NSString * s in [numberWordString componentsSeparatedByString:#" "]){
// Let the formatter turn each string into an NSNumber, then get
// the stringValue from that, which will be a digit.
[digitString appendString:[[nf numberFromString:s] stringValue]] ;
}
NSLog(#"%#", digitString); // prints "312"
Obviously, you'll have to put some work in to handle different input formats, lowercase, bad input (this will crash if nf fails to format -- it'll return nil which is an illegal argument to appendString:), etc.

Converting a string character-by-character to an NSMutableString in loop

I'm trying to get each individual character of a string, gameWord, into an NSMutableArray.
I'm converting each char into an object and then adding the objects into the NSMutableArray with the following code:
NSMutableString *letterOne = [[NSMutableString alloc] initWithFormat:#"%C",[gameWord characterAtIndex:0]];
NSMutableString *letterTwo = [[NSMutableString alloc] initWithFormat:#"%C",[gameWord characterAtIndex:1]];
and so on....
What I don't understand is why the following loop won't work?
NSMutableArray *lettersArray = [[NSMutableArray alloc] initWithCapacity:[gameWord length]];
for (int x = 0 ; x <= [gameWord length] ; x++) {
NSMutableString *letter = [[NSMutableString alloc]initWithFormat:#"%C",[gameWord characterAtIndex:x]];
[lettersArray addObject:letter];
[letter release];
}
Would really appreciate help understanding this problem and any other ways of getting each character of an NSString into individual objects (not chars).
Your for loop goes beyond the length of the string. Arrays are zero-based, so the last object has the index [gameWord length] - 1.
This should work:
for (int x = 0; x < [gameWord length]; x++) {
//... ^--changed from <= to <
}