Parsing arithmetic expression for long numbers that need formatting - cocoa-touch

I am trying to make a simple calculator app. Currently, the app works perfectly. One problem: It's smart enough to change results into formatted numbers (800000 = 800,000), but not full expressions (200*600/21000 = 200*600/21,000).
I would like to be able to have a method that I could feed a string and get back a string of properly formatted numbers with operations still inside the string.
Example:
I feed the method 30000/80^2. Method gives back 30,000/80^2.
EDIT: People seem to be misunderstanding the question (Or it's possible I am misunderstanding the answers!) I want to be able to separate the numbers - 60000/200000 would separate into 60000 & 200000. I can do it from there.

Well, what's the problem? You obviously can parse the whole expression (you say calculator works), you can format single numbers (you say you can format results).
The only thing you need is to parse the expression, format all the numbers and recompose the expression...
EDIT: There is a simpler solution. For formatting, you don't need to parse the expression into a tree. You just have to find the numbers.
I suggest to create character set of all operators
NSCharacterSet* operators = [NSCharacterSet characterSetWithCharactersInString:#"+*-/^()"];
NSCharacterSet* whitespaces = [NSCharacterSet whitespaceCharacterSet];
Then split the expression using this set:
NSString* expression = [...];
NSMutableString* formattedExpression = [NSMutableString string];
NSRange numberRange = NSMakeRange(0, 0);
for (NSUInteger i = 0; i < expression.length; i++) {
unichar character = [expression characterAtIndex:i];
if ([whitespaces characterIsMember:character] || [operators characterIsMember:character]) {
if (numberRange.length > 0) {
NSString* number = [expression substringWithRange:numberRange];
NSString* formattedNumber = [self formatNumber:number];
[formattedExpression appendString:number];
numberRange.length = 0;
}
}
else if (numberRange.length == 0) {
numberRange.location = i;
numberRange.length = 1;
}
else {
numberRange.length++;
}
if ([operators characterIsMember:character]) {
[formattedExpression appendFormat:#"%C", character];
}
}
if (numberRange.length > 0) {
NSString* number = [expression substringWithRange:numberRange];
NSString* formattedNumber = [self formatNumber:number];
[formattedExpression appendString:number];
}
Note that this should work even for numbers prefixed by a sign. I am ignoring all whitespaces because if you want to have a pretty expression, you probably want to handle whitespaces differently (e.g. no space after (, space before +/-, space after - only if it's not a number sign...). In general, for handling spaces, parsing the expression into a tree would simplify matters. Also note that infix expressions are not unambiguous - that means that you should sometimes add parenthesis. However, that can't be done without parsing into a tree.

Look up NSNumberFormatter. Not only will that handle formatting of numbers, it will do so based on the user's locale.

Related

Replacing character within cstring - getting bad access

Is it possible to replace a character from a c string after converting it from NSString via the UTF8string method?
For example take the code below. It is to format a string with particular rule.
- (NSString *)formatString:(NSString *)input {
if (input.length==0) {
return #"";
}
//code to determine rule
....
....
// substitute output format with input characters
if (rule) {
input = [input substringFromIndex:prefix.length];
char *string = (char *)[rule UTF8String];
int repCount = 0;
for (int i=0; i<rule.length; i++) {
if (string[i] == '#') {
if (repCount < input.length)
string[i] = [input characterAtIndex:repCount++];//bad access
else
string[i] = ' ';
}
}
NSMutableString *output = [NSMutableString stringWithCString:string encoding:NSUTF8StringEncoding];
...
... //do something with the output
return output;
} else {
return input;
}
}
Initially string[0] has '#' and it should get replaced with the character in the input. This is not happening.
In a word, NO. That buffer doesn't belong to you so leave it alone.
A couple of issues:
You are casting UTF8String, which returns a const char *, to char *. UTF8String is, by definition, returning a read-only string and you should use it as such. (You really should use casts sparingly, if at all. Certainly never use casts to override const qualifiers for variables.)
If you want to perform this C-string manipulation, you have to copy the string to your own buffer. For example, use getCString or getCharacters methods (but only after you've created a buffer to receive them, and remember to add a character for the NULL terminator).
By the way, you're also returning characterAtIndex, which is a unichar (which can be larger than 8-bits), and using it in your char * buffer (8-bits per character). I'd be wary about mixing and matching those without being very careful. It is best to pick one and stick with it (and unichar offers a little more tolerance for those non-8-bit characters).
Perhaps you check for this earlier, but you're setting string to be those characters after the prefix, and then proceed to check the next rule.length number of characters. But, as far as I can tell, you have no assurances that string actually has that many characters left in it. You should test for that, or else that will also cause problems.
Personally, I'd retire this whole C-string algorithm and employ the appropriate NSString and/or NSMutableString methods to do whatever replacement you wanted, e.g. stringByReplacingCharactersInRange, stringByReplacingOccurrencesOfString, or the equivalent NSMutableString methods, replaceCharactersInRange or replaceOccurrencesOfString.

Parse CSV with double quote in some cases

I have csv that comes with format:
a1, a2, a3, "a4,a5", a6
Only field with , will have quotes
Using Objective-C, how to easily parse this? I try to avoid using open source CSV parser as company policy. Thanks.
I agree with rmaddy that a full csv parsing algorithm is beyond the scope of SO, however, here is one possible way of tackling this:
Extract the whole line into an NSString
Iterate over the NSString, pushing each character back into another string.
When a comma is encountered, save the stored string, ignore the comma and create a new blank string.
Repeat until end of line.
Set a flag to identify whether double quotes have been found. If the flag is set, ignore step 3. When a second set of quotes is found, unset the flag and continue as before.
This is generally applicable to any language (using their respective native string classes) and such an algorithm can form a small basis for a full CSV parser. In this particular case however, you may not need any more functionality than this.
For some sample code, I would encourage you to have a look at my answer to this CSV-related question as it demonstrates a way of splitting and storing strings in Objective-C.
This snippet worked perfectly for me...
BOOL quotesOn = false;
NSString* line = #"a1, a2, a3, "a4,a5", a6";
NSMutableArray* lineParts = [[NSMutableArray alloc] init];
NSMutableString* linePart = [[NSMutableString alloc] init];
for (int i = 0; i < line.length; i++)
{
unichar current = [line characterAtIndex: i];
if (current == '"')
{
quotesOn = !quotesOn;
continue;
}
if (!quotesOn && current == ',')
{
if (linePart.length > 0)
[lineParts addObject: linePart];
linePart = [[NSMutableString alloc] init];
}
if (quotesOn || current != ',')
[linePart appendString: [line substringWithRange: NSMakeRange(i, 1)]];
}
if (linePart.length > 0)
[lineParts addObject: linePart];
My 5 elements are in the lineParts array...

Concatenating an int to a string in Objective-c

How do I concatenate the int length to the string I'm trying to slap into that array so it is "C10" given length == 10, of course. I see #"%d", intVarName way of doing it used else where. In Java I would of done "C" + length;. I am using the replaceObjectAtIndex method to replace the empty string, "", that I have previously populated the MSMutableArray "board" with. I am getting an error though when I add the #"C%d", length part at the end of that method (second to last line, above i++).
As part of my homework I have to randomly place "Chutes" (represented by a string of format, "C'length_of_chute'", in this first assignment they will always be of length 10 so it will simply be "C10") onto a game board represented by an array.
-(void)makeChutes: (int) length {// ??Change input to Negative number, Nvm.
//??Make argument number of Chutes ??randomly?? across the board.
for(int i = 0; i < length;){
int random = arc4random_uniform(101);
if ([[board objectAtIndex:random] isEqual:#""]) {
//[board insertObject:#"C%d",length atIndex:random];
[board replaceObjectAtIndex:random withObject:#"C%d",length];
i++;
}
}
}
Please ignore the extra and junk code in there, I left it in for context.
In Objective-C the stringWithFormat method is used for formatting strings:
NSString *formattedString = [NSString stringWithFormat:#"C%d", length];
[someArray insertObject:formattedString];
It's often easier to create your formatted string on a line of its own in Objective-C, since as you can see the call can be fairly verbose!

Objective C How to detect one or multiple spaces in a NSString

I have seen one answer for my task, but I couldnt find it now.
I want to detect whether a string has empty word and contains at least "" or " " (two spaces" or more multiple spaces, OR not.
If not, I would add this to Nsmutablearray.
If yes with empty or at least one space, I would like it not to be written to mutablearray.
How to solve this?
EDIT 12 October 2011:
Guys, thank you.
Again I am sorry that I was unclear about my wish. I wanted to check if a string is empty or contains whitespaces without any character. I have posted my answer below.
Im not sure whether it is the most performant way of doing it but you could split your array and see whether the length is greater than 1:
if ([string componentsSeparatedByString:#" "].count > 1)
http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/Reference/NSString.html
if( bookmarked.length == 0 )
{
NSLog (#"not allowed: empty");
}
else if ([[bookmarked stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] length] == 0)
{
NSLog (#"not allowed: whitespace(s)");
}
else
{
[bookmarklist addObject:bookmarked];
}
Depends if you're looking for ANY whitespace or just spaces. For spaces you can use:
if( [string length] == 0 ||
!NSEqualRanges( [string rangeofString:#" "],
NSMakeRange(NSNotFound, 0) ) )
{
// either the string is empty or we found a space
} else {
// we didn't find a space and the string is at least of length 1
}
If any whitespace, use the whitespace character set:
if( [string length] == 0 ||
!NSEqualRanges( [string rangeOfCharacterFromSet:
[NSCharacterSet whitespaceCharacterSet]],
NSMakeRange(NSNotFound, 0) ) )
{
// either the string is empty or we found a space
} else {
// we didn't find a space and the string is at least of length 1
}
Replace whitespaceCharacterSet with whitespaceAndNewlineCharacterSet if you like.
Look at the documentation for NSString.
Specifically, look under the section Finding Characters and Substrings for the method you want, probably you want to use – rangeOfString:options:range: multiple times.
Also, look under the section Replacing Substrings for the method you want, probably you want to use – stringByReplacingOccurrencesOfString:withString:options:range:
Take a look at the NSRegularExpression class and coding examples.
NSString *myString = #"ABC defa jh";
int spaceCount = [[myString componentsSeparatedByString:#" "] count] - 1;
if (!spaceCount) {
// Zero spaces, Do Something
} else if (spaceCount <= 2) {
// 1-2 spaces add this to NSMutableArray (although the wording about what you wanted to do in each case is confusing, so adjust for your needs)
} else {
// 3+ spaces, Do Not add this to NSMutableArray (adjust for your needs)
}

get the first letter of each word in a string using objective-c

Example of what I am trying to do:
String = "This is my sentence"
I am looking to get this as a result: "TIMS"
I am struggling with objective-c and strings for some reason
Naïve solution:
NSMutableString * firstCharacters = [NSMutableString string];
NSArray * words = [#"this is my sentence" componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
for (NSString * word in words) {
if ([word length] > 0) {
NSString * firstLetter = [word substringToIndex:1];
[firstCharacters appendString:[firstLetter uppercaseString]];
}
}
Note that this is kinda stupid about breaking up words (just going by spaces, which isn't always the best approach), and it doesn't handle UTF16+ characters.
If you need to handle UTF16+ characters, change the if() statement inside the loop to:
if ([word length] > 0) {
NSString * firstLetter = [word substringWithRange:[word rangeOfComposedCharacterSequenceAtIndex:0]];
[firstCharacters appendString:[firstLetter uppercaseString]];
}
You could always use the method cStringUsingEncoding: and just iterate the const char*. Or better, you could use the method getCharacters:
When you iterate, you just have to do a for loop and check if the previous character is the ' ' character and append it to your temporary variable. If you want it uppercase, just use uppercaseString at the end.
see apple doc for more information:
http://developer.apple.com/mac/library/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/Reference/NSString.html#//apple_ref/occ/instm/NSString/getCharacters:range:
I also struggle with strings sometime, the function names are not really similar to other languages like c++/java for instance.
The shortest and faster way to enumerate through the string using below code
Swift
let fullWord = "This is my sentence"
var result = ""
fullWord.enumerateSubstrings(in: fullWord.startIndex..<fullWord.endIndex, options: .byWords) { (substring, _, _, _) in
if let substring = substring {
result += substring.prefix(1).capitalized }
}
print(result)
Output
TIMS