CS 509, Advanced Programming II

Spring 2004 - Test 2


  1. You have been asked to add a vector-like .capacity() member function to lists. Describe what .capacity() for lists would do how you would implement it.


    Capacity for vectors indicates the total amount of space available for storing elements before the vector has to be resized to add more space for more elements. Because lists always have the exact amount of space needed to store elements (or, alternatively and equivalently, lists are always resized when inserting an element), .capacity() for lists would be equivalent to .size().

    There are other ways to implement lists, which would lead to different answers to this question. For example, the list implementation could cache deleted nodes on a free list, which would save the cost of repeatedly allocating and deleting nodes. In this case, .capacity() would return the sum of the sizes of the data and free lists.

    Most answers got across the relation between .capacity() and allocation. One answer identified list capacity with .max_size(), which would mean that (under the interpretation here), lists would never allocate more space.


  2. Vector iterators can be invalidated. Can vector indices be invalidated? Explain.


    Yes, indices can be invalidated. Suppose i is set to the index of the left-most even integer in a vector v. Doing v.insert(v.front(), 1) would invalidate i in the sense that it no longer points to the left-most even integer.

    On the other hand, indices are not invalidated when the vector is resized to grow: an index to the ith element in the old vector is still an index to the ith element in the new vector.

    Most answers to this question were correct, but chose another approach than the one given in the previous paragraph, pointing out that the second .at() in

    i = v.size() - 1;
    v.at(i);
    v.pop_back();
    v.at(i);
    

    would blow up even though i hadn't changed. The wrong answers were wrong because they claimed that indices couldn't be invalidated.


  3. Given a set of strings, a colleague believes that the following greedy strategy will provide maximal ouroboros compression for the strings in the set:

    1. As long as the set contains more than one string, repeat steps 2 and 3.

    2. Overlap pairwise all strings in the set, keeping track of the pair P with the largest amount of overlap.

    3. Remove from the set the two strings that make up the largest-overlap pair P and add P to the set.

    Unfortunately, your colleague's strategy doesn't work; provide a test case in which your colleague's algorithm might fail to produce a maximally-compressed string.

    Hints: Your test case only needs three strings. You should consider the case when there is more than one largest-overlap pair P in step 2.


    Your colleague's greedy solution to Assignment 2 works as long as the greedy choice is unambiguous; that is, there is only one largest-overlap pair to pick. As soon as there is more than one largest-overlap pair, you colleague's algorithm can fail.

    To see why, consider the three strings are abb, bb, and bbc as input. The pair-wise maximum overlap is 2 for the pairs abb (abb + bb) and abbc (abb + bbc). If the algorithm chooses abb, then the next step leads to the maximally compressed string abbc. However, if the algorithm chooses abbc, then the next step is to form the final compressed string abbcbb, which is not maximally compressed.

    The wrong answers for this question were interesting, because they chose a test case in which the final compressed string size was the same no matter which choice was made. The correct answers followed mostly the argument in the previous paragraph.


  4. A colleague of yours doesn't like iterator ranges where the second iterator in the range points to one past the last element in the range. Instead, your colleague uses iterator ranges in which the second iterator points to the last element in the range, and points to one before the first element to indicate an empty range.

    Given the iterator range (b, e) following your colleague's scheme, write a loop to iterate through the range, assuming b and e are input iterators. Make sure your code can handle all possible (valid) ranges.


    The straightforward solution is

    while (b != e)
      process(b)
      b++
    

    that is, march b to the right until it hits e. This solution works for one-past-the-end iterator ranges, but doesn't work here because your colleague's iterator ranges are at-the-end ranges. In particular, b == e is not a termination condition; it describes an iterator range with only one element.

    We can fix this problem by repeating the processing step after the loop. We know that if the loop terminates, then b == e, so we're justified in doing

    while (b != e)
      process(b)
      b++
    
    // b == e here
    
    process(e)
    

    We have handled the case where b is to the left of e, and when b is equal to e, but thanks to your colleague's scheme, we still have to consider the case when b is to the right of e.

    Mercifully, you colleague has made this (relatively) easy for us. The end iterator is one to the left of the begin iterator for empty ranges, which tempts us to try something like

    if (e + 1 != b)
      while (b != e)
        process(b)
        b++
      process(e)
    

    Unfortunately, e is an input iterator and doesn't support + int; we have to do instead

    if (++e != b)
      while (b != e)
        process(b)
        b++
      process(e)
    

    But this isn't quite right, because after doing ++e, e is now pointing to an element not in the range, which means the final process() is now incorrect, a problem easily fixed.

    if (++e != b)
      while (b != e)
        process(b)
        b++
    

    Finally, note that ++e != b has the same logical value as b != e, which means that we can get rid of the if statement (but not moving e one to the right):

    ++e
    while (b != e)
      process(b)
      b++
    

    Note that our correct solution is just our original incorrect solution with ++e in front. This leads us to the a second way to solve this problem (one you are probably wondering if I would ever get around to): Your colleague's iterator-range scheme (at-the-end) can be turned into the traditional iterator-range scheme (one-past-the-end) by just moving the end one to the right.

    First, because you had an old version of the test, it asked you to write a for loop. This was revised to just a loop in the current version of the test because suggesting a for loop was misleading. Because of this mistake, I did not grade the answer to this question on the basis of the loop used.

    But the loop didn't make any difference anyway: all the answers to this question were wrong, and they were wrong due to invalid iterator operations. Most answers included something like

    for ( ; b < e; b++)
    

    Everybody apparently ignored the requirement that b and e are input iterators, and input iterators don't support the less-than operator.



This page last modified on 25 February 2004.