I am having pretty hard time understanding the assignment 2 of the iPhone Application Development fall 2011. Starting point for this Assignment is right after you watch The Lecture 4- Views. You can also watch Xcode and Source Code Management because the same code is entered again. After you made modifications to the Calculator Brain you should update your app so that all of the things from Assignment 1 work here as well.
Lets get started. I will be honest here. I read the assignment few times and I didn't understood it completely. I had few questions. One of them is: how should a user enter a variable inside the Calculator's program? I was looking at the screenshots and I saw three new buttons for variables A,B and X? Does this mean there will be only three variables? After reading whole assignment few more times I think that other variables are possible via the external API. You can probably send as many as you want using the NSDictionary and variableValues from the task one. So I stopped asking myself all those questions and start programming. If you have a different explanation feel free to share it.
TASK 1:
Read the whole text of the task 1 of the Assignment 2 in the PDF file. This is the short version an some stuff is left out.
1. Add the capability to your CalculatorBrain to accept variables as operands (in
addition to still accepting doubles as operands). You will need new public API in
your CalculatorBrain to support this.
You must add a new version of the runProgram: class method with the following
signature ...
+ (double)runProgram:(id)program
usingVariableValues:(NSDictionary *)variableValues;
In addition, create another class method to get all the names of the variables used in a
given program (returned as an NSSet of NSString objects) ...
+ (NSSet *)variablesUsedInProgram:(id)program;
If the program has no variables return nil from this method (not an empty set).
SOLUTION: I will start out with variablesUsedInProgram: It looks easier to me. Under hint number 5 you will find you should use create helper function that checks if something is operation or not. So I am going to create that first.
+(BOOL)isOperation:(NSString *)operation{ NSSet *supportedOperations = [[NSSet alloc] initWithObjects:@"+",@"-",@"*",@"/",@"sin",@"cos",@"sqrt",@"π", nil]; return [supportedOperations containsObject:operation] }
The idea behind VariablesUsedInProgram: is this. First I check if my program is an array. This is just a safety measure. I go through all the elements of the program. I check if they are strings. If they are I check if they are I check if teha are operation. If they are not I add them to my mutable set. if the set is empty I return nil just as the assignment instructed.
+(NSSet *)variablesUsedInProgram:(id)program{ NSMutableSet *_variablesUsedInProgram = [[NSMutableSet alloc] init]; if([program isKindOfClass:[NSArray class]]){ for (id element in program){ if ([element isKindOfClass:[NSString class]]){ if(![self isOperation:element]){ [_variablesUsedInProgram addObject:element]; } } } } if ([_variablesUsedInProgram count]==0) {return nil;} else {return _variablesUsedInProgram;} }
I had a bit of help with this one. I have came u with this solution thanks to 8vius from Google Groups. Thanks man.
Now for the runProgram:usingVariableValues: class method. First step is to check if the program is an array and to create mutable copy. Same as in the original runProgram method. then I inspect every element on the stack. If it is an operation just skip it. Else if it is a variable from dictionary replace variable with number. Else replace a variable name with zero, because you don't know what is the value of the variable. Then just let the recursion do the math.
+(double) runProgram:(id)program usingVariableValues:(NSDictionary *)variableValues{ NSMutableArray *stack; int i=0; if([program isKindOfClass:[NSArray class]]){ stack=[program mutableCopy]; } for (i=0;i<stack.count;i++){ if ([[stack objectAtIndex:i] isKindOfClass:[NSString class]]){ for (id key in variableValues){ if ([self isOperation:[stack objectAtIndex:i]]) { //leave it alone, it is an operation }else if([[stack objectAtIndex:i] isEqual:key]){ [stack replaceObjectAtIndex:i withObject:[variableValues objectForKey:key]]; } else { [stack replaceObjectAtIndex:i withObject:[NSNumber numberWithInt:0]]; } } } } return [self popOperandOffStack:stack]; }
Solutions to the rest of the tasks form Assignment2 are soon to follow. As always I am here to learn so comment. This is my solution and it doesn't have to be correct so if you spot some bugs or know smarter way of writing this code let me know in the comments below.
In fact this assignment is only an upstart to the next one. It is not really a useful application. It just allows you to create a programmable calculator, which can use 1 or more variables. The value of these variables is at the moment fixed. Bes ure to put that value in the Controller otherwise you might have to redo things on the next assignment.
ReplyDeleteIndeed you need to check whether a variable is not the same as a supported operation. The isOperation: method is one solution. I decided to take another approach. As a variable is set through a method pushOperandAsVariable:, I can add a prefix there. Then a variable will always be uniquely defined. Drawback of this approach that setting variables have to pass through this method, which makes the class less generic.
ReplyDeleteYour runProgram:usingVariableValues is a bit heavy. For each calculation of a specific value you make a copy of the stack in order to evaluate. And that you need to do in the next assignment. There is a much lighter solution.
ReplyDeleteI leave it at that for the moment, so you can think over it.
Hi Arnaud,
DeleteWhat do you mean by heavy? For each call of the runProgram:usingVariableValues: I create only one copy of the array. I copy the array and make it mutable. This is because I want to replace all variable names with their values. So I make one pass through the whole array to make the replacements.
Then I call the recursion
popOperandOffStack:stack
But runProgram:usingVariableValues: is not the part of the recursion.
I think my runProgram:usingVariableValues: is very similar to runProgram: method demonstrated in the lecture.
Is there simpler solution?
Yes. I did see that. But what happens if you need to do it for 10.000 different variable values and a complicated equation?
DeleteI give up :) How did you write the method? I guess you optimized replacing part of the algorithm?
DeleteNow you analyse the stack for the occurence of variables each time you want to evaluate the equation.
DeleteHowever you could also do the work when calculating the result in popOperandOfStack with an extra else if. i.e. else if variable use the corresponding value.
That seems a bit lighter to me, but in practice probably does not matter. For assignment 3 you will do more calculations.
Anyway best luck with the next assignments. Love discussing different solutions.
Thank you. This was very helpful for me. As i can see the "+(double) runProgram:(id)program usingVariableValues:(NSDictionary *)variableValues" should be directly called from CalculatorViewController. Agrument program and variableValues should come from the CalculatorViewController. Is it right?
ReplyDeleteHi Theepan,
Deletethat is right, runProgram:usingVariableNames: should be called from CalculatorViewController. Just like the plain runProgram: is called.
Hi Tomislav, finally I've got time to starting with Assigment 2. Your post was very helpful to me, and I would like to suggest for your solution and for all your blog readers, the use of "setWithObjects" method for creating or updating an NSSet instead of the alloc-init procedure.
ReplyDeleteThe advantage of using setWithObject and avoiding alloc-init is that setWith... methods take care about memory allocation and deallocation; using alloc-init force you to manage memory and could generate a lot of memory leaks.
Hi. The solution looks good to me. I've pretty much gone the same way...
ReplyDeleteI think that that the original runProgram method should be pointed towards the new implementation, to maximise reuse - e.g.
return [self runProgram:program usingVariableValues:nil];
Also, it's reasonably straightforward to create test cases to test out the solution, using the CalculatorBrain to create the program... This helped me a lot :-)
I'm still trying to figure out the next task, but I've added my version of this task to my blog at http://www.i4-apps.com
Many thanks for this post, helped me a great way understanding task 1 as I had the same problems as you (where do the variables come from?). By reading your post I realized that we just need to prepare the model for the *possibility* of variables and not care about the rest.
ReplyDeleteWhat caught my eye though is that in "+(NSSet *)variablesUsedInProgram:(id)program" you're handing back "_variablesUsedInProgram" as result.
Couldn't this provoke a serious problem as you're returning a NSMutableSet * when a NSSet * is expected? You could just "return [_variablesUsedInProgram copy];" instead, or is this intentional?
I'm quite new to OOP, thus the question.
I didn't read the second half of the post though, I didn't want to spoil my pleasure by just retyping some code, so can't comment on that ;)
Hello.
ReplyDeleteThanks everyone for the hints and solutions,
I just tried to complete this damn second assignment and I just stuck. I spent for it a lot of time and didn't get solution.(Just gave up). It helped me to understood that the way to the software engineering(and programming at all ) is closed for me, because I m just too stupid for it...:(