Notes on the 2008 Exam A hastily compiled review of this year s exam. Apologies if there are errors. Please also read notes on previous years exams especially the first set of notes. Question 1 Question is similar to Practical 6. But the inner loop requires a FOR to read 8 subj/mark pairs, (instead of the inner sentinel controlled while in practical 6). The key points here are correct use of a while loop to read data till a sentinel (student_no == 999 ) is reached (6 marks).; there is a standard template for this. use of a FOR loop (or equivalent) to read the 8 pairs. (4 marks) 2 marks for noting fails (using a Boolean variable fail ) and printing the subject code (i.e. the if (mark_per_exam < 40) bit) printing F if any subject was failed if (fail) (3 marks) remaining marks for initializations, the else if s for printing grades, computing and printing the average, #include<iostream> #include<string> #include<iomanip> using namespace std; const int NUM_EXAMS = 8; int main() string student_no, letter_grade, exam_code; double average, tot; int mark_per_exam; bool fail; //true if i've found a failed exam cout << fixed << setprecision(2); cin >> student_no ; while (student_no!= "999") //initialise variables for this student tot = 0; fail = false;
cout << student_no << "\tfailed: "; for (int i=0; i < NUM_EXAMS; i++) cin >> exam_code >> mark_per_exam; //update variables for computing average tot = tot + mark_per_exam; //handle fails if (mark_per_exam < 40) fail = true; cout << exam_code << " "; //end FOR loop average = tot/num_exams; cout << endl << "Average:" << average << "\tgrade: "; if (fail) //if any exam was failed letter_grade = "F"; else if (average >= 70) letter_grade = "D"; else if (average >= 60) letter_grade = "P1"; else if (average >= 50) letter_grade = "P2"; else if (average >= 40) letter_grade = "P3"; cout << letter_grade << endl << endl; cin >> student_no; //end while student_no!= 999 return 0; The code file is available and the sample data. To run it save both files from the web page, and at the Terminal type: make Q108./Q108 < Q108data.txt Question 2
NB This solution is lifted from Solution notes for Exam 2005 where there are extensive helpful comments. Note that this year, we didn t do this in a practical but we talked about it under Lecture Topic 10. (a) 3 marks for any 3 of the following main points I was looking for: functions extract commonality, abstract and simplify, hide detail functions break up a problem naturally allow delegation of parts of the problem to members of a team facilitate error-checking and step-by-step development allow re-use of code that has been developed for related problems (b) 3 marks Parameters are the means by which a main program communicates values to a function. Call-by-value parameters are when it is the values of the variables that are passed to the function. For example f (x) when x is 6 causes the value 6 to be given to the function f. The function cannot change the value of x. Call-by-reference parameters causes a variable, that is a place in which a value can be stored, to be passed to the function, and the function can then change the value in that variable. Call-by-reference is achieved by placing a & after the data type specifier for the parameter in question. For example void f (int& x) means that the thing passed in as x is a reference to a value that the function can update. (c) 7 marks 1 mark for the declaration; 2 for the if statement; 4 for some loop that achieved the required effect. See notes from 2005. int gcd (int n1, int n2) int i; //make i be the smnaller of the two numbers if (n1 < n2) i = n1; else i = n2; while (!((n1%i == 0) && (n2%i == 0))) //while i does not divide evenly into both n1 and n2 i--; //now i divides into both n1 and n2 return i; (d) 7 marks
3 marks for the declaration, especially the &s; 2 for calling gcd properly; 2 for updating n and d but not trying to return them. void simplify_fraction (int& n, int& d) int g = gcd (n,d); n = n / g; d = d / g; return; Question 3 Based on Practical 8 but required you to use a function, and required you to stop when approx squared was close to n, not when successive approxs were close to each other. See Q308.cpp on the web for a complete solution. (a) 12 marks roughly 5 marks for the bits that work out if our current approx is good enough; 1 of these reserved for using a function; marks taken off for using the other test for close_enough since that meant you learned off the practical solution instead of solving the question posed here. 4 marks for improving the approx using Newton s formula; 1 of these reserved for using a function. 3 for loop control and proper management of the variable. Note that nothing in part (a) of this question is concerned with init being n/2, precision being 0.0001, terminating when n = 0, or with reading in n or writing out the answer. These all come into play in part (b), being about how we want to use newtonsqrt this particular time. Another user of newtonsqrt might want to use it to compute sqrts of numbers obtained elsewhere, might want to pass the sqrts to someone else, might be happy with a precision of 0.001 or even 0.1 if they were dealing in very big numbers, and might have a better handle on what was a good initial approximation!! In general no function should cin or cout unless specifically designed to do so!! E.g. read_array or print_results functions. double newtonsqrt(double n, double init, double precision) double approx = init; while (!(close_enough (approx, n, precision))) approx = better_approx (approx,n); return approx; double better_approx (double x, double n) return (n/x + x)/2; bool close_enough (double x, double n, double precision) //return true if absolute value of (x*x - n) is v small
return (fabs (x*x - n) < precision); Without the helper functions it would look like this 5 lines of code would give you 9-10 of the 12 marks for part (a) double newtonsqrt(double n, double init, double precision) double approx = init; while (fabs (approx*approx n) > precision) approx = (n/approx + approx)/2; return approx; (b) 8 marks 4 marks for calling newtonsqrt properly, including using n/2 as initial n, and either printing the value, or assigning it to a variable and then printing it. Note that newtonsprt(n, n/2, 0.0001); cout << approx; is wrong because you don t save the value returned by newtonsqrt anywhere. 3 mark for the sentinel controlled loop; had to stop at n=0, not n<0; 1 mark for declarations and output. int main () double n; double approx; cout << "Enter a number whose square root you require:"; cin >> n; while (n!= 0) approx = newtonsqrt (n, n/2, 0.00001); //n/2 is an arbitrary initial approximation. //0.00001 is the value for precision cout << "The sq root of " << n; cout << " is approx " << approx << endl; cout << "Enter a number whose square root you require; 0 to terminate:"; cin >> n; return 0;
Question 4 This question was way too easy. But those who are repeating probably didn t get it!! Note however that the recursion question will be a bit harder in the supplemental exam. (a) 4 marks A recursive function is one which calls itself. (b) (i) 2 marks for each f(2,4) is 16. f(10,3) is 1000. Only one mark if you failed to carry the computations to their conclusion. If you were at any of the lectures remember how we went down a row of students till the base case was reached and then ran back along compiling the answer. In this case the students in the line would have to multiply the answer by a (either 2 or 10) as it came back up. (ii) 4 marks The power function, a to the power of b, for integers. (c) 8 marks int fact (int n) if (n == 0) return 1; else return (n * fact (n-1)); Max 3 marks if there was no recursive call. The key to recursive functions involving integers is to identify the base case, and then identify how to go from the solution for n-1 to a solution for n. In this case you multiply the answer for n-1 by n: that is n! is n times n-1!. Question 5 This was the arrays question. It was a simplification of the ideas in Practical 17. Simpler in that there was no need to compute a running average. A total score per course and a count of scores per course allows us to compute the average at the end. Also we are guaranteed that there will not be more codes than 50 so loop control is simpler. 3 marks for declarations of arrays and their capacity. 4 roughly for the search function or its equivalent. 3 for the sentinel controlled loop (note that overall you could get a lot of marks in this exam for just getting sentinel controlled loops right!!!) 5 for identifying the need to, and adding new slots (the if (i == -1) part) 2 for adding to tot and incrementing count 3 for the printing FOR loop.
The key concept is that the arrays represent the output in preparation rather than the input. So they are summary data what codes have appeared, how many times and what has been the total score given. When we read a code we need to find out have we seen it before (search(code, codes, num_codes)). If we haven t we add a new slot to the codes array, and corresponding slots to the count and tots array, initialized to 0. (Alternatively, like in the Practical 17 solution, set new slots to the input values, and have an else to handle the case where the code existed already.) int search (string s, string sarray[], int size); //returns the position of s in sarray, or -1 if not found const int CAPACITY = 50; const string SENTINEL = "XXX"; int main () //set up three parallel arrays; string codes [CAPACITY]; int counts [CAPACITY]; int tots [CAPACITY]; string code; int score; int num_codes = 0; cin >> code; while (code!= SENTINEL) //find code in the array or add it int i = search (code, codes, num_codes); if (i == -1) //code was not found in the array i=num_codes;//i is where the next code should go codes[i] = code;//store the new code tots[i] = 0; //initialise for now counts[i] = 0; num_codes++; //update num_codes //whether the code was new or not we are //now ready to add the score into slot i cin >> score; tots[i] = tots[i] + score; counts[i]++; //read the next code cin >> code;
//report results cout << " Code Number Average\n"; for (int i=0; i < num_codes; i++) cout << setw(5) << codes[i] << setw(5) << counts[i]; cout << fixed << setprecision(2) << setw(9); cout << tots[i]*1.0/counts[i] << endl;// the 1.0 forces the division to be noninteger return 0; int search (string s, string sarray[], int size) //returns the position of s in sarray, or -1 if not found for (int i=0; i<size; i++) if (sarray[i] == s) return i; //if we get here we didn't find s return -1; Other Miscellaneous Comments that I noted while grading: Sentinel Controlled Loops Template is cin >> x; while (x!= ) *** cin > x;
Where the *** are you put all processing of the current valid x. The processing of x must all be inside the loop. I had many examples of cin >> x; while (x!= ) cin > x; tot = tot+x; more stuff on x. But those last two lines must be inside the loop, before the cin > x statement. Obviously instead of x, you might have studentno or code or n or whatever it is that you are reading that will eventually be the sentinel instead of a valid value. Many people used the above template (with ***s in it or in it!!!!) every time they thought a while loop was needed. The template is ONLY for SENTINEL CONTROLLED input. Look carefully above for other kinds of while loops in this exam: gcd and newtonsqrt. Functions Many of these comments or similar appear in Notes for 2005 exam. Local variables and accessing a function s value: If a function looks like this int f(int x) int ans; ans = x; return ans; you can t do this z = 4; f(z); cout << ans; // wrong ans here is not related to f s ans and expect it to print the value of f(z) ans only has meaning inside f. Even if you declared ans in the calling program, that would be a separate variable from the one f uses, which is a local variable whose scope in the function f. The calling program has no access to f s ans. The correct way to call f and use the value it returns is: cout << f(z);
or int x = f(z); //now use or print x Accessing parameters: With the following declaration of f int f(int x, int y) the body of f has access to variables called x and y, and the values in there are the ones provided by the calling function. Therefore, f should not redeclare these variables read values for these variable initialize these variables So more than likely these are all wrong: int f(int x, int y) int x, y; //creates new local variables x and y which //effectively hides the ones passed in as parameters int f(int x, int y) cin >> x >> y; //if it succeeds in reading data it will replace // the values the calling function provided. int f(int x, int y) y=0; //if the calling function provides f with a value for y //f should use it!! Not replace it. And, unrelated to parameters, but the f declared above should not print any values, but must return its answer. So NOT int f(int x, int y) cout << (x*y)/2; return 0; but probably something like int f(int x, int y) return (x*y)/2;
IFs inside loops inside functions: This is a more subtle problem than the ones above. It came up in gcd and in the search function for Q5. Start by distinguishing between the two functions: and int f1(int n) for (int i=0; i<n; i++) if g(i) return i; return -1; int f2(int n) int ans=-1; for (int i=0; i<n; i++) if g(i) ans = i; return ans; Both functions will return an i for which g(i) is true, and both will return -1 if g(i) is not true for any i. However f1 will return the first i for which g(i) is true it returns immediately it finds such an i. Whereas f2 will return the last i for which g(i) is true it always goes through all the i-s from 0 to n-1. If you don t see that, try this one. How many times will the loop repeat? What values cold possibly be returned? int f (int n) for (int i=0; i<n; i++) if g(i) return i; else return -1; Because both branches of the IF statement return something, the first time we reach the IF statement the function will return something and the loop and f will be exited. Either f will return 0 (if g(0) is true) or it will return -1. The function will never get a chance to try i=1. Good luck!