This Winter 2015 CS107 Dress Rehearsal Exam includes problems adapted from past CS107 exams by Jerry Cain and Michael Chang. CS107 Winter 2015 Cynthia Lee February 10th, 2015 CS107 Midterm Dress Rehearsal Examination PLEASE NOTE: In the interest of providing you with as much practice as possible, this ended up a touch on the long side. After consideration of what to cut, I decided that it would be better not to cut any of these valuable problems, and simply alert you to this issue for calibration purposes. So, for your reference, I think it’s about ½-problem too long. This is a closed book, closed note, closed computer exam. You have 120 minutes to complete all problems. You don’t need to #include any libraries, and you needn’t use assert to guard against any errors. Understand that the majority of points are awarded for concepts taught in CS107, and not prior classes. You don’t get many points for for-loop syntax, but you certainly get points for proper use of &, *, and the low-level C functions introduced in the course. Good luck! SUNet ID: _____________________ Last Name: _____________________ First Name: _____________________ I accept the letter and spirit of the honor code. I’ve neither given nor received aid on this exam. I pledge to write more neatly than I ever have in my entire life. [signed] __________________________________________________________ Score Grader 2. CMultiSet ______ ______ 2. accumulate generic ______ ______ 3. Bits ______ ______ Total ______ ______ 1. strseparate 2 Problem 1: strseparate You will implement the following function: char *strseparate(char **stringp, const char *delimiters); Provided strseparate is properly implemented, this program static void printTokens(const char *sentence) { char copy[strlen(sentence) + 1]; strcpy(copy, sentence); char *curr = copy; while (curr != NULL) { char *word = strseparate(&curr, ". !"); // note curr is updated by reference printf("\"%s\"\n", word); } } int main(int argc, const char *argv[]) { printTokens("This is a sentence!!"); return 0; } outputs the following: "This" "is" "a" "sentence" "" "" strseparate examines the C string reachable from stringp, locates the first occurrence of any character (including the '\0') in delimiters, and overwrites it with'\0', thereby truncating the string The address of the character following the freshly written '\0' (or NULL, if the end of the C string addressed by stringp was reached) is stored in the space addressed by stringp, and the original char * addressed by stringp is returned. Over the lifetime of the sample program above, the copy buffer evolves from this: T h i s h i s i s i s a s e n t e n c e s e n t e n c e ! ! \0 into this: T \0 \0 a \0 \0 \0 \0 and curr, which initially addresses the 'T', will be updated to address the second 'i', then the 'a', and so on. 3 Problem 1 [continued] Present your implementation of strseparate on the space below. The only string.h function you may (and, in fact, are required to) use is strchr, which is much like strstr, except that it searches for a character instead of a string. char *strchr(const char *str, int c); The strchr function locates the first occurrence of c (converted to a char) in str, and returns a pointer to the located character (or NULL if the character isn’t present). Feel free to tear out the previous page so you can more easily refer to it. ANSWER: The most straightforward approach was to step through the C string addressed by *stringp, one character and a time, and relying on strchr to tell us when it’s found a delimiter character. static char *strseparate(char **stringp, const char *delimiters) { char *front = *stringp; char *end = front; while (strchr(delimiters, *end) == NULL) end++; *stringp = *end == '\0' ? NULL : end + 1; *end = '\0'; return front; } strchr even finds the '\0' of delimiters if you pass 0 as the second argument, though it was fine to include a special case for *end != '\0' in the while loop (my first pass at this actually did just that.) Interestingly enough, many of you invoked strchr on *stringp for each character in delims instead of the other way around. That can be made to work, but it requires much more code to determine which delimiter character appears before the others. 4 Problem 2: The CMultiSet A multiset is the generalization of a mathematical set, except that multiset elements are permitted to appear multiple times. We’ll define a generic CMultiSet to store client elements along with the number of times each element appears. Here are two useful lines from the cmultiset.h file: typedef struct CMultiSetImplementation CMultiSet; typedef int (*CMultiSetComparisonFn)(const void *elemAddr1, const void *elemAddr2); The implementation stores elements as a CVector, where each CMultiSet entry (elemSize bytes each) is represented by an elemSize + sizeof(int)-byte CVector entry (for the element itself, and its count). Note that the CVector entries aren’t structs, but rather manually managed blobs of memory. The cmultiset.c file would start off like this: struct CMultiSetImplementation { CVector *elements; int elem_size; CMultiSetComparisonFn cmpfn; }; CMultiSet *cmultiset_create(int elemSize, CMultiSetComparisonFn cmpfn) { CMultiSet *cms = malloc(sizeof(CMultiSet)); cms->elements = cvec_create(elemSize + sizeof(int), 0, NULL); cms->elemSize = elemSize; cms->cmpfn = cmpfn; // comparison function knows how to compare client elements return cms; // no need to store freefn, as it’s been installed into elements } Your job is to implement cmultiset_add function. It ensures that a shallow copy of the item addressed by addr is stored within the encapsulated CVector. If the item addressed by addr is already present, increment the count. If the item addressed by addr is being added for the first time, then you should append a new shallow copy to the end of the CVector, with count of 1 (don’t bother sorting). You should rely on CVectorSearch to decide if the element is already present. You must figure out how to prepare the image of something that can be appended to the elements field via a call to cvec_append. Use the provided CMultiSetComparisonFn at the appropriate times. void cmultiset_add(CMultiSet *cms, const void *addr) { int found = CVectorSearch(cms->elements, addr, cms->cmpfn, 0, false); if (found == -1) { char image[cms->elemSize + sizeof(int)]; memcpy(image, elemAddr, cms->elemSize); *(int *)(image + cms->elemSize) = 1; CVectorAppend(cms->elements, image); } else { char *match = CVectorNth(cms->elements, found); (*(int *)(match + cms->elemSize))++; } } Problem 3: The accumulate generic 5 Consider the following two functions, noticing that they have very similar structure: int int_array_product(const int array[], size_t n) { int result = 1; for (size_t i = 0; i < n; i++) { result = result * array[i]; } return result; } double double_array_sum(const double array[], size_t n) { double result = 0.0; for (size_t i = 0; i < n; i++) { result = result + array[i]; } return result; } You are to implement a generic accumulate function that captures the shared structure of these two funcions. It takes the base address of an array and its effective length, the array element size, the function callback that should be repeatedly applied (above it would be multiply and add, but implemented as 2argument functions rather than operators directly), the address of the default/starting value (to play the role of 1 and 0.0 in the above code), and the address where the overall result should be placed. The function type of the callback is as follows: typedef void (*BinaryFunc)(void *partial, const void *next, void *result); Any function that can interpret the data at the first two addresses, combine them, and place the result at the address identified via the third address falls into the BinaryFunc function class. (The const appears with the second address, because it’s expected that the array elements—the elements that can’t be modified—be passed by address through the next parameter.) a) First, implement the generic accumulate routine. We’ve provided some of the structure that should contribute to your implementation. You should fill in the three arguments needed so that memcpy can set the space addressed by result to be identical to the space addressed by init, and then go on to fill in the body of the for loop. This first part was designed to expose basic memory and pointer errors very early on—e.g. to confirm that weren’t dropping &’s and *’s where they weren’t needed. void accumulate(const void *base, size_t n, size_t elem_size, BinaryFunc fn, const void *init, void *result) { memcpy(result, init, elem_size); for (size_t i = 0; i < n; i++) { const void *next = (char *) base + i * elem_size; fn(result, next, result); } } 6 b) Now reimplement the int_array_product function from the previous page to leverage the accumulate function you just implemented for part a). Assume the name of the callback function passed to accumulate (which you must implement) is called multiply_two_numbers. static void multiply_two_numbers(void *partial, const void *next, void *result) { *(int *)result = *(int *)partial * *(const int *)next; } // preserving the constness in the casts wasn’t necessary int int_array_product(const int array[], size_t n) { int identity = 1, product; accumulate(array, n, sizeof(int), multiply_two_numbers, &identity, &product); return product; } 7 Problem 4: Bits Match each bitwise expression on the left to an equivalent C expression from the choices on the right. Assume the variable x is a 32-bit two’s complement int. INT_MAX and INT_MIN are the maximum and minimum representable signed integers. Shifts on signed values are arithmetic, not logical, shifts. (x >> 31) * x a) 0 Answer:(i) b) 1 c) -1 d) INT_MAX e) INT_MIN f) x (x | (x - 1)) == x h) x Answer:(m) (x ^ -x) Answer:(a) g) –x & 1 == 1 i) (x > 0) ? 0 : -x j) (x > 0) ? -x : 0 k) (x > 0) ? 0 : x l) (x > 0) ? x : 0 m) (x % 2) != 0 n) (x % 2) == 0 CVector Functions typedef int (*CompareFn)(const void *addr1, const void *addr2); typedef void (*CleanupElemFn)(void *addr); typedef struct CVectorImplementation CVector; CVector *cvec_create(int elemsz, int capacity_hint, CleanupElemFn fn); void cvec_dispose(CVector *cv); int cvec_count(const CVector *cv); void *cvec_nth(const CVector *cv, int index); void cvec_insert(CVector *cv, const void *addr, int index); void cvec_append(CVector *cv, const void *addr); void cvec_replace(CVector *cv, const void *addr, int index); void cvec_remove(CVector *cv, int index); int cvec_search(const CVector *cv, const void *key, CompareFn cmp, int start, bool sorted); void cvec_sort(CVector *cv, CompareFn cmp); void *cvec_first(CVector *cv); void *cvec_next(CVector *cv, void *prev); Other Functions void void void void void *memcpy(void *dest, const void *src, size_t n); *memmove(void *dest, const void *src, size_t n); *malloc(size_t size); *realloc(void *ptr, size_t size); free(void *ptr); size_t strlen(const char *s); char *strcpy(char *dest, const char *src); char *strncpy(char *dest, const char *src, size_t n); char *strdup(const char *s); char *strcat(char *dest, const char *src); 8
© Copyright 2024