CS107 Practice Midterm Exam - Programming Paradigms - 21, Exams of Programming Paradigms

Introduction to computer science. CS107 Practice Midterm Exam with solution of Programming Paradigms. Prof. Cain - Stanford University

Typology: Exams

2010/2011

Uploaded on 10/11/2011

larryp
larryp 🇺🇸

4.8

(34)

352 documents

1 / 40

Toggle sidebar

This page cannot be seen from the preview

Don't miss anything!

bg1
CS107 Handout 21
Spring 2008 April 30, 2008
CS107 Practice Midterm Exam
Exam Facts
Normal Time: Wednesday, May 7
th
at 7:00 p.m. in Hewlett 200.
The exam is open-notes, open-book, closed-computer. You should bring your own lecture notes, the course
handouts, and any printouts of assignment code you’ll want to consult during test time.
This practice exam is constructed from actual midterms I’ve given in previous quarters. You will effectively
have as much time as you want, although we will need to pull the exams at 10:00 p.m., since that’s when the
room needs to be locked back up. Those on campus who need to take the exam earlier in the day on
Wednesday may do so, provided you start some time after 9:00 a.m. (but not during the hour that I teach. )
Material
The exam will focus on material like that covered on the first five assignments. Be prepared for C/C++ coding
questions requiring strong understanding of pointers, references, arrays, function pointers, and low-level
memory manipulation, as well as questions on code generation, function call and return, variable layout, stack
and heap implementation, and the compilation process as covered in Assignment 5.
In general, a good way to study for the coding questions is to take a problem for which you have a solution
(lecture or section example, homework problem, sample exam problem) and write out your solution under test-
like conditions. This is much more valuable than a passive review of the problem and its solution where it is too
easy to conclude "ah yes, I would have done that" only to find yourself adrift during the real exam when there is
no provided solution to guide you!
This handout is intended to give you practice solving problems that are comparable in format and difficulty to
those which will appear on your midterm. Understand that I am under no obligation to mimic this exam when
writing yours. That being said, any material covered in lecture or on an assignment is fair game.
pf3
pf4
pf5
pf8
pf9
pfa
pfd
pfe
pff
pf12
pf13
pf14
pf15
pf16
pf17
pf18
pf19
pf1a
pf1b
pf1c
pf1d
pf1e
pf1f
pf20
pf21
pf22
pf23
pf24
pf25
pf26
pf27
pf28

Partial preview of the text

Download CS107 Practice Midterm Exam - Programming Paradigms - 21 and more Exams Programming Paradigms in PDF only on Docsity!

CS107 Handout 21

Spring 2008 April 30, 2008

CS107 Practice Midterm Exam

Exam Facts

Normal Time: Wednesday, May 7th^ at 7:00 p.m. in Hewlett 200.

The exam is open-notes, open-book, closed-computer. You should bring your own lecture notes, the course

handouts, and any printouts of assignment code you’ll want to consult during test time.

This practice exam is constructed from actual midterms I’ve given in previous quarters. You will effectively

have as much time as you want, although we will need to pull the exams at 10:00 p.m., since that’s when the

room needs to be locked back up. Those on campus who need to take the exam earlier in the day on

Wednesday may do so, provided you start some time after 9:00 a.m. (but not during the hour that I teach. ☺)

Material

The exam will focus on material like that covered on the first five assignments. Be prepared for C/C++ coding

questions requiring strong understanding of pointers, references, arrays, function pointers, and low-level

memory manipulation, as well as questions on code generation, function call and return, variable layout, stack

and heap implementation, and the compilation process as covered in Assignment 5.

In general, a good way to study for the coding questions is to take a problem for which you have a solution

(lecture or section example, homework problem, sample exam problem) and write out your solution under test-

like conditions. This is much more valuable than a passive review of the problem and its solution where it is too

easy to conclude "ah yes, I would have done that" only to find yourself adrift during the real exam when there is

no provided solution to guide you!

This handout is intended to give you practice solving problems that are comparable in format and difficulty to

those which will appear on your midterm. Understand that I am under no obligation to mimic this exam when

writing yours. That being said, any material covered in lecture or on an assignment is fair game.

Problem 1: San Francisco Fine Dining

Consider the following struct definitions:

typedef struct { int **garydanko; int aqua[3]; char *quince; } appetizer;

dessert *dinnerisserved(short *boulevard, appetizer *jardiniere); int *bonappetit(dessert azie, char **indigo) { appetizer oola; dessert *catch;

azie.bacar[azie.ame[2]] += catch->farallon.aqua[4]; ((appetizer *)(((dessert )(&oola.quince))->farallon.garydanko))->quince = 0; return (dinnerisserved((short *) &indigo, &oola)).farallon.aqua; }

Generate code for the entire bonappetit function. Be clear about what assembly code corresponds to what line. You have this and the next page for your code.

typedef struct { short ame[2]; appetizer farallon; char bacar[8]; } dessert;

line 1 line 2 line 3

Problem 3: The C multiset

A multiset operates much like the hashset from Assignment 3, except that the multiset maintains a count on

how many times each element has been inserted. The similarities between hashset and multiset are otherwise

so great that it makes sense to layer the multiset right on top of the hashset.

Our multiset is fully generic, so the client should be able to use one to store any type he or she wants. But each

item needs to be accompanied by its multiplicity. So each element is packed up against a sizeof(int)-byte

integer and stored in the encapsulated hashset.

The difficult part here is the manual construction of these variably-sized figures so that they can be inserted into

the encapsulated hashset.

Here’s the reduced interface file for the multiset:

typedef int (MultiSetHashFunction)(const void elem, int numBuckets); typedef int (MultiSetCompareFunction)(const void elem1, const void elem2); typedef void (MultiSetMapFunction)(void *elem, int count, void auxData); typedef void (MultiSetFreeFunction)(void *elem);

typedef struct { hashset elements; int elemSize; MultiSetFreeFunction free; } multiset;

void MultiSetNew(multiset *ms, int elemSize, int numBuckets, MultiSetHashFunction hash, MultiSetCompareFunction compare, MultiSetFreeFunction free); void MultiSetDispose(multiset *ms); void MultiSetEnter(multiset *ms, const void *key); void MultiSetMap(multiset *ms, MultiSetMapFunction map, void *auxData);

Your job is to implement all four functions, leveraging as much as possible off of the hashset routines

implemented in Assignment 3.

Some additional information:

  • You needn’t worry about alignment restrictions.
  • You don’t need to bother with any error checking whatsoever.
  • The manner in which client elements and their count get stored in the enclosed hashset must be consistent with the drawing specified above.
  • MultiSetHashFunctions know how to hash client elements, not element/integer pairs. The client has no knowledge how things are stored behind the scenes.
  • MultiSetCompareFunctions know how to compare two client elements. It’s the comparison of the two client elements that guides the search through the encapsulated hashset.
  • Since you’re technically a client of the hashset, you shouldn’t break the abstraction wall by accessing the hashset's fields.

elemSize bytes sizeof(int) bytes

a. Present implementations of MultiSetNew and MultiSetDispose. These should be short and

straightforward.

typedef int (MultiSetHashFunction)(const void elem, int numBuckets); typedef int (MultiSetCompareFunction)(const void elem1, const void elem2); typedef void (MultiSetFreeFunction)(void *elem);

/**

  • Function: MultiSetNew

  • Initializes the raw space addressed by ms to be an empty otherwise
  • capable of storing an arbitrarily large number of client elements of the
  • specified size. The numBuckets, hash, compare, and free parameters
  • are all supplied with the understanding that they’ll be passed right
  • right through to HashSetNew. You should otherwise interact with the
  • embedded hashset using only those functions which have the authority
  • to access the hashset's fields. */

void MultiSetNew(multiset *ms, int elemSize, int numBuckets, MultiSetHashFunction hash, MultiSetCompareFunction compare, MultiSetFreeFunction free) {

  • Function: MultiSetDispose

  • Disposes of all previously stored client elements by calling
  • HashSetDispose. */

void MultiSetDispose(multiset *ms) {

Problem 4: The Queen Of Parking Infractions

Parking on campus has become so laughably difficult that some have resigned themselves to parking illegally and

just paying whatever parking tickets they get. In the interest of crowning the Queen of Parking Infractions, you’ve

been handed a fully constructed and populated multiset of license plate numbers, where the multiplicity is

understood to be the number of parking tickets recorded for a particular license plate. Because all license plate

numbers are strings of at most 7 characters, the multiset stores eight-byte figures, which are really static

character arrays storing null-terminated C strings of length seven or less.

Your job is to complete the implementation of FindQueenOfParkingInfractions, which takes the address of a

fully constructed multiset of license plates, and populates the specified character buffer with the license plate for

which the maximum number of parking tickets has been recorded. You should use the convenience struct I’ve

provided.

typedef struct { const char *licensePlate; int numTickets; } maxTicketsStruct;

void FindQueenOfParkingInfractions(multiset *ms, char licensePlateOfQueen[]) {

Problem 5: Short Answers

For each of the following questions, we expect short, insightful answers, writing code or functions only when

necessary. Clarity and accuracy of explanations are key.

a. Assuming all instructions and pointers are 4 bytes, explain why instructions of the form M[x] = M[y] +

M[z], where x, y, and z are legitimate but otherwise arbitrary memory addresses, aren't included in the instruction set.

b. Our activation record model wedges the return address information below the function arguments and above

the local parameters. Why not pack all variables together, and place this return address at the top or the bottom of the activation record?

c. Write a short function called IsLittleEndian, which returns true if and only if the computer architecture is

little endian—that is, if the least significant byte of a multi-byte figure is stored at the lowest address instead of the highest one.

CS107 Handout 21S Spring 2008 April 30, 2008

CS107 Practice Midterm Solution

Problem 1: San Francisco Fine Dining

Consider the following struct definitions:

typedef struct { int **garydanko; int aqua[3]; char *quince; } appetizer;

dessert *dinnerisserved(short *boulevard, appetizer *jardiniere); int *bonappetit(dessert azie, char **indigo) { appetizer oola; dessert *catch;

azie.bacar[azie.ame[2]] += catch->farallon.aqua[4]; ((appetizer *)(((dessert )(&oola.quince))->farallon.garydanko))->quince = 0; return (dinnerisserved((short *) &indigo, &oola)).farallon.aqua; }

Generate code for the entire bonappetit function. Be clear about what assembly code corresponds to what line. You have this and the next page for your code.

The full activation record layout is large as far as CS107 activation records go. The picture is here, and the code is on the next page.

typedef struct { short ame[2]; appetizer farallon; char bacar[8]; } dessert;

line 1 line 2 line 3

saved PC

SP before decrement

SP after decrement catch

oola.garydanko

oola.aqua[0]

oola.aqua[1]

oola.aqua[2]

oola.quince

azie.ame[0…1]

azie.farallon.garydanko

azie.farallon.aqua[0]

azie.farallon.aqua[1]

azie.farallon.aqua[2]

azie.farallon.quince

azie.bacar[0…3]

azie.bacar[4…7]

indigo

Problem 3: The C multiset

a. void MultiSetNew(multiset *ms, int elemSize, int numBuckets, MultiSetHashFunction hash, MultiSetCompareFunction compare, MultiSetFreeFunction free) { HashSetNew(&ms->elements, elemSize + sizeof(int), numBuckets, hash, compare, free); ms->elemSize = elemSize; ms->free = free; }

/**

  • Function: MultiSetDispose

  • Disposes of all previously stored client elements by calling
  • HashSetDispose. */

void MultiSetDispose(multiset *ms) { HashSetDispose(&ms->elements); }

b. void MultiSetEnter(multiset *ms, const void *elem) { void *found = HashSetLookup(&ms->mappings, elem); if (found == NULL) { char pair[ms->elemSize + sizeof(int)]; memcpy(pair, elem, ms->elemSize); *(int )(pair + ms->elemSize) = 1; HashSetEnter(&ms->mappings, pair); } else { if (ms->free != NULL) ms->free(found); memcpy(found, elem, ms->elemSize); ((int *)((char *) found + ms->elemSize))++; } }

c. typedef struct { MultiSetMapFunction originalMap; void *originalAuxData; int elemSize; } mapHelper;

static void ApplyMultiMapFunctionWithAuxData(void *elem, void *auxData) { mapHelper *helper = auxData; int multiplicity = *(int *)((char *) elem + helper->elemSize); helper->originalMap(elem, multiplicity, helper->originalAuxData); }

void MultiSetMap(multiset *ms, MultiSetMapFunction map, void *auxData) { mapHelper helper = { map, auxData, ms->elemSize }; HashSetMap(&ms->elements, ApplyMultiMapFunctionWithAuxData, &helper); }

Problem 4: The Queen Of Parking Infractions (5 points)

typedef struct { const char *licensePlate; int numTickets; } maxTicketsStruct;

static void IdentifyContender(void *elem, int count, void *auxData) { maxTicketsStruct *ticketData = auxData; if (count > ticketData->numTickets) { ticketData->numTickets = count; ticketData->licensePlate = elem; // cast not needed, but fine to have it } }

void FindQueenOfParkingInfractions(multiset *ms, char licensePlateOfQueen[]) { maxTicketsStruct ticketData = { NULL, 0 }; MultiSetMap(ms, IdentifyContender, &ticketData); strcpy(licensePlateOfQueen, ticketData.licensePlate); }

Problem 5: Short Answers

For each of the following questions, we expect short, insightful answers, writing code or functions only when

necessary. Clarity and accuracy of explanations are key.

a. Assuming all instructions and pointers are 4 bytes, explain why instructions of the form M[x] = M[y] +

M[z], where x, y, and z are legitimate but otherwise arbitrary memory addresses, aren't included in the instruction set.

If x, y, and z are legitimate but otherwise arbitrary memory address, then the instruction would need 32 bits to encode any one of them. Since the entire instruction is confined by a 32-bit limit, there’s no way to pack information about three address and the opcode for addition into the instruction.

b. Our activation record model wedges the return address information below the function arguments and

above the local parameters. Why not pack all variables together, and place this return address at the top or the bottom of the activation record?

The return address can't be placed on the bottom because the calling function doesn't know where the bottom of the full layout is. The caller knows nothing of what locals are allocated by the callee's instruction list.

The return address could, in most cases, be placed at the top since the callee function generally knows what all of the parameters are and how large everything is. The key exception—the reason that this as a convention could not be adopted—is that some functions, such as printf and scanf, take a variable number of arguments. In those situations, no information about the number and size of the parameters is around, so it can't reliably understand where the stored PC would be.

CS107 Handout 25S Spring 2008 May 14 th, 2008

CS107 Midterm Solution

Problem 1: Your Friendly Green Grocer (15 points: 5, 4, and 6)

Consider the following struct definitions:

typedef struct { int apple; char *banana; char cherry[16]; } fruit;

typedef struct { short pea[6]; fruit potato; fruit *parsnip[3]; } veggie;

fruit *tort(fruit **fig, int grape); fruit *casserole(veggie carrot, veggie *spinach) { fruit date; date.cherry[4] = spinach->pea[carrot.potato.apple]; ((veggie *)(((veggie *)carrot.parsnip[0])->parsnip))->potato.banana = *(char **) &date; return tort(&(spinach->parsnip[2]), date.banana[4]) + 10; }

Generate code for the entire casserole function. Be clear about what assembly code corresponds to what line. You have this and the next page for your work.

line 1 line 2

line 3

carrot.potato.apple

carrot.potato.banana

carrot.peas[0…1]

veggie.potato.ban

ana

carrot.peas[2…3]

carrot.peas[4…5]

carrot.potato.cherry[0…3]

carrot.potato.cherry[4… ]

carrot.potato.cherry[8… 1]

carrot.potato.cherry[12…15]

carrot.parsnip[0]

carrot.parsnip[1]

carrot.parsnip[2]

spinach

saved pc

date.apple

date.banana

date.cherry[0…3]

date.cherry[4…7]

date.cherry[8…11]

date.cherry[12…15]

SP

// allocation of date SP = SP - 24;

// line 1 R1 = M[SP + 40]; // load carrot.potato.apple R2 = R1 * 2; // scale by sizeof(short) R3 = M[SP + 76]; // load spinach, which is also &spinach->peas[0] R4 = R3 + R2; // compute address of short identified on rhs R5 =.2 M[R4]; // load that short M[SP + 12] =.1 R5 // populate date.cherry[4] with one-byte version

Criteria for Line 1 (5 points)

  • Loads carrot.potato.apple and spinach using the correct offsets: 1 point
  • Properly scales the carrot.potato.apple value by sizeof(short) == 2: 1 point
  • Properly computes the address of the right hand side r-value: 1 point
  • Loads relevant short into a register using .2 : 1 point
  • Stores one-byte version of that short to date.cherry[4] : 1 point

// line 2 R1 = M[SP]; // load the pretend char * overlaying date.apple R2 = M[SP + 64]; // load carrot.parsnip[0] M[R2 + 52] = R1; // drop R1 in make-believe banana of make-believe veggie

Criteria for Line 2 (4 points)

  • Loads the right hand side value properly with the correct number of loads: 1 point
  • The sum of the offsets in whatever solution they provide add up to 116: 1 point
  • The proper number of loads and stores are used to update memory as it should be: 2 points (all or nothing,

because this is super important)

// line 3 R1 = M[SP + 76]; // load spinach again R2 = R1 + 44; // load address of parsnip[2] within record addressed by R R3 = M[SP + 4]; // load date.banana R4 =.1 M[R3 + 4]; // load date.banana[4] SP = SP – 8; // make space for function call M[SP] = R2; // write down param 0 value M[SP + 4] = R4; // write down param 1 value CALL // jump, except RV to be populated with fruit * SP = SP + 8; // clean up params, expect RV to have fruit * RV = RV + 240; // advance RV by 10 * sizeof(fruit)

Criteri for Line 3 (6 points)

  • 1 point for the proper number of dereferences used in synthesizing param values: point
  • 1 point for the correct offsets (or sum of offsets if they lose the first point): 1 point
  • Handles the char -> int conversion: 1 point
  • Sets up the parameters correctly: 1 point
  • Allocates and deallocates space for the params as well: 1 point
  • Updates the RV by updating the current value to be 240 more.

// deallocation of date SP = SP + 24; RET;

b. (7 points) Now implement HashSetEnter , which manages to copy the element address by elem into the

hashset addressed by hs. It uses the quadratic internal probing technique, as described above, to find a home for the new element. HashSetEnter returns true if the new element gets inserted into a previously unoccupied bucket, and false if the new element replaces a previously inserted one. (Don’t worry about rehashing the hashset if more then three quarters of the buckets are occupied. You’ll worry about that in part c.) Use this and the next page for your work. Don’t worry about alignment restrictions.

bool HashSetEnter(hashset *hs, void *elem) { if (hs->count > (3 * hs->alloclength / 4)) HashSetRehash(hs); // you’ll implement this function for part c

int hashcode = hs->hashfn(elem, hs->alloclength); int delta = 0;

while (true) { hashcode += delta; int bucket = hashcode % hs->alloclength; bool *occupied = (bool *) NthEntry(hs->elems, bucket, hs->elemsize);

void targetAddr = occupied + 1; if (!occupied) { // it goes here, and we return true *occupied = true; memcpy(targetAddr, elem, hs->elemsize); hs->count++; return true; } else if (hs->cmpfn(elem, targetAddr) == 0) { if (hs->freefn != NULL) hs->freefn(targetAddr); memcpy(targetAddr, elem, hs->elemsize); return false; } delta++; } }

Criteria for Problem 2b (7 points)

  • Properly invokes the hash function to establish a base hash code for the entire enter process: 1 point
  • Manages to generate the sequence of bucket numbers using the triangular number sequence, all modulo hs- >alloclength : 1 point
  • Properly computes the address of the bucket to be examined for any given iteration: 1 point (it’s okay to double ding if they manually computed the address from scratch, but not triple ding. If they wrote a function or macro like I did, then don’t even double ding.)
  • Properly dispatches between the three insertion scenarios: bucket unoccupied versus bucket occupied by identical element versus bucket occupied by different element: 1 point
  • Calls memcpy or memmove properly: 1 point
  • Conditionally frees the old element if hs->freefn is non- NULL : 1 point
  • Increments hs->count when required, and returns the correct Boolean value: 1 point

c. (5 points) Finally, implement the HashSetRehash function, which updates the hashset addressed by

hs so that it has twice as many buckets and all of the elements are rehashed to take up residence in a bucket where they could have resided had the new number of buckets been the number of buckets all along. (You’ll benefit by figuring out how to call HashSetEnter to help with the redistribution.)

static void HashSetRehash(hashset *hs) { hashset clone; memcpy(&clone, hs, sizeof(hashset));

hs->count = 0; hs->alloclength *= 2; hs->elems = malloc(hs->alloclength * EntrySize(hs->elemsize)); for (int i = 0; i < hs->alloclength; i++) *(bool *)NthEntry(hs->elems, i, hs->elemsize) = false;

for (int i = 0; i < clone.alloclength; i++) { bool *occupied = (bool ) NthEntry(clone.elems, i, hs->elemsize); if (occupied) { void *elem = occupied + 1; HashSetEnter(hs, elem); } }

free(clone.elems); }

Criteria for Problem 2c (5 points)

There are several approaches to this problem that will work just fine. My approach is a little more clever than

I’d expect from anyone coding by hand in a timed situation. But the overall effect of whatever you wrote needs to

be the same.

  • Properly allocates a new block of memory twice as big, and updates the alloclength field to be twice as large: 1 point
  • Manually manages the reallocation using malloc instead of realloc : 1 point
  • Properly rehashes and copies over all client elements from the old block to the new one: 2 points (don’t deduct faulty pointer arithmetic here unless they did it differently than they did for parts a and b)
    • Properly visits each and every bucket in the old figure: 1 point
    • Conditionally rehashes and copies over, ideally using HashSetEnter to do so: 1 point
  • Frees the old block when everything’s been copied over: 1 point
  • Accepts the address of the full data image storing
  • all of the friendship information, and constructs
  • and returns a dynamically allocated array of person
  • structs storing exactly the same information.
  • @param image the base address of the entire data image,
  • as described on the previous page.
  • @return the address of a dynamically allocated array
  • of person records, where each record stores
  • all of the friendship information about one
  • person in the original data image. */

person *decompress(void *image) { int numPeople = *(int *)image; person *people = malloc(numPeople * sizeof(person));

int offset = sizeof(int); for (int i = 0; i < numPeople; i++) { char *name = (char *) image + offset; people[i].name = strdup(name); offset += strlen(name) + 1; while (offset % sizeof(char *) > 0) offset++; people[i].numfriends = *(int *)((char *) image + offset); people[i].friends = malloc(people[i].numfriends * sizeof(char *)); offset += sizeof(int); char **friends = (char **)((char *) image + offset); for (int j = 0; j < people[i].numfriends; j++) { people[i].friends[j] = strdup(friends[j]); } offset += people[i].numfriends * sizeof(char *); }

return people; }

Criteria for Problem 3 (10 points)

  • Properly extracts the total number of people: 1 point (0 points if cast is incorrect, deduct at most 2 points across entire problem for cast-related issues)
  • Properly allocates a person array of just the right size: 1 point
  • Clearly understands the need to maintain an offset or some construct so that it knows where each inlined record begins: 1 point
  • Manages to properly maintain this offset for the lifetime of execution: 2 points (1 point dedicated specification to the padding issue between the name and number of friends)
  • Fins the embedded name and makes a strdup or malloc / strcpy version of it and plants it in the name field: 1 point
  • Properly extracts the number of friends: 1 point
  • Properly allocations space for that many **char *** s and places it in the friends field: 1 point
  • Uses the proper **(char ) casting to establish a base address for all the friend **char *** s: 1 point
  • Places a deep copy of the each name in one of the **char *** slots in the array: 1 point

Mehran Sahami Handout

CS106A October 24, 2007

Practice Midterm Examination

Midterm Time: Tuesday, October 30, 7:00–8:30P.M.

Midterm Location: Kresge Auditorium

Portions of this handout by Eric Roberts and Patrick Young

This handout is intended to give you practice solving problems that are comparable in format and

difficulty to those which will appear on the midterm examination next Tuesday.

Exam is open book, open notes, closed computer

The examination is open-book (specifically the course textbook The Art and Science of Java and

the Karel the Robot coursereader) and you may make use of any handouts, course notes/slides,

printouts of your programs or other notes you've taken in the class. You may not, however, use a

computer of any kind (i.e., you cannot use laptops on the exam).

Coverage

The midterm exam covers the material presented in class through today (October 24th), which

means that you are responsible for the Karel material plus Chapters 1 through 9 of the class

textbook The Art and Science of Java , plus the use of mouse listeners from Chapter 10 (sections

10.1-10.4). Data files (which will be covered in class on Friday) are not covered on the exam.

General instructions

Answer each of the questions included in the exam. Write all of your answers directly on the

examination paper, including any work that you wish to be considered for partial credit.

Each question is marked with the number of points assigned to that problem. The total number

of points is 90. We intend for the number of points to be roughly comparable to the number of

minutes you should spend on that problem.

In all questions, you may include methods or definitions that have been developed in the course,

either by writing the import line for the appropriate package or by giving the name of the

method and the handout or chapter number in which that definition appears.

Unless otherwise indicated as part of the instructions for a specific problem, comments will not

be required on the exam. Uncommented code that gets the job done will be sufficient for full

credit on the problem. On the other hand, comments may help you to get partial credit if they

help us determine what you were trying to do.

Blank pages for solutions omitted in practice exam

In an effort to save trees, the blank pages that would be provided in a regular exam for writing

your solutions have been omitted from this practice exam. In the real exam, we would certainly

provide the blank pages for you to write your solutions.