Comp210 Lab 9: Analyzing Graph-Search; Vectors

(Any questions about using read? Ask now, or forever hold your peace!)

Table of Contents

  • Analyzing Graph-Search
  • Vectors in Scheme
  • Vectors in C
  • Analyzing Graph-Search

    Recall from last Friday's lecture, the graph-searching algorithm we used, to see if there was a path from Houston to Vancouver. We realized that in order to avoid an infinite loop, we could simply remember whether or not we'd previously visited a city, and only search cities we hadn't already searched from.

    (This is an instance of the general concept of avoiding doing work twice: if we've already searched for a path from Denver and found no answer, somehow remember that fact rather than do the work over again. This is the same idea behind caching a web page.)

    There were two approaches mentioned in lecture, to keep track of what cities we'd already seen.

    1. Keep an accumulator of cities already seen; when we visit a city, first check that it's not already in the accumulator to avoid visiting a city twice.
    2. Similar to the previous, but instead of an accumulator, keep a list already-seen, and modify this with set! as we visit new cities.
    3. Within the city structure itself, add a boolean value seen?; when we visit a city, first check whether city-seen?.
    Which approach do you prefer? Is one approach better than the others?

    Let's compare. Require the library Projects/lab9-library.ss, which contains:

    (define-structure (city name nbhrs seen?))
    
    It also has code for reachable2? and reachable3?. These functions use approaches 2 and 3 above to to ask whether one city is reachable from another. (It also provides placeholders houston and sanFran whose values are cities.)

    (We provide the code, but you should be capable of writing it on your own. Note that we're dealing with cities, as well as lists of cities. So there is one function to handle cities, and another function to handle lists of cities. They happen to each call each other, which is fine.)

    In reachable2, how does the size of the list already-seen act as the program runs? When visiting a city, how long does it take to find out whether you've already seen it?

    When you call reachable2? twice in a row, how does it behave? What do you need to do between computations?

    How does reachable2 compare with reachable3?? Time these two functions when searching for a path between sanFran and houston. (remember that time-apply expects a function of zero arguments). There are about 100 cities defined, and approximately 1002/2=5,000 edges.

    Food for thought: What about approach 1, passing an accumulator around? I claim that while this approach gives the correct answer, it can be catastrophically worse than either of the two other approaches. Why? Write the code and do a small hand-eval to find out.)

    Vectors in Scheme

    In Monday's lecture we were introduced to the functions vector , vector-ref , vector-set! , and build-vector .
    Exercise:
  • Make a vector of 10 elements, whose ith element is (* (- i 4) (- i 7)).
  • Write a function hasNeg which accepts two arguments: a vector v, and a number N. hasNeg returns #t if v contains a negative value among its first N elements, and #f otherwise. (Note that N is a number of elements, not an index.) You may assume that (<= N (vector-length v)).
    Hint: The way this problem is set up, it's easier to look at the elements in descending order (using structural recursion), than to look at elements in ascending order with generative recursion. But how would you look at the elements in ascending order?
  • Vectors in C

    C also has vectors (in C circles, they use the word "arrays"). Here are some Scheme expressions, and the corresponding C expressions.
    (define nums (vector 8 9 10))           int nums[] = { 8, 9, 10 };
    (vector-ref nums 2)                     nums[2];
    (vector-set! nums 2 1000)               nums[2] = 1000;
    (vector-length nums)                    (no equivalent)
    
    In both cases, we have one vector, which contains three elements. The elements are indexed zero through two. Saying (vector-ref nums 3) or nums[3] is an error. We'll have more to say about this below.

    C's while-loop

    C does not have an equivalent to build-vector. Instead, you must declare a vector of the desired size, and then go through and set each element of the array yourself. Here is an example. (Note the new way of declaring an array in C, not seen above).
                                            int i=0, nums[3];
    (define nums                            // This part must be inside a function:
      (build-vector 3                         while ( i < 3 ) {
                    (lambda (i)                 nums[i] = 2*i;
                            (* 2 i))))          i = i + 1;
                                                }
    

    The above code uses a while loop: while the condition i < 3 is true, repeatedly do the statements inside the following curly-braces. When the condition (hopefully) becomes false, continue on with whatever code follows the loop.

    Exercise:Hand-evaluate the C code.

    C's while-loop can be seen as a particular way of using recursion. We will see in lecture that we can write our own version of loops in Scheme, using the same ideas of recursion we've used all along.

    Index-out-of-bound Errors

    We mentioned earlier, that if a vector nums has three elements (dimensions), then they are indexed zero through two, and it is a (common) error to refer to (vector-ref nums 3) or nums[3].

    However, the Scheme and C handle this error in very different ways. Scheme lets you know immediately that there is an error; C doesn't! Instead, C might:

  • Immediately produce the cryptic error "segmentation fault" and stop the program (without telling you where the problem occurred), or
  • change the value of other random placeholders without telling you, and continue merrily on, which causes your program to crash five minutes later. Even if you could find out where this crash occurred, it wouldn't help you find the original bug.
  • Because of change the value of other placeholders, it might even finish but give you a wrong answer (without letting you know of this possibility).
  • any of the above, varying from time-to-time of running your program.
  • This is brain-damaged. In short, remember to access arrays from 0 to one less than their declared size!.

    #include <stdio.h>
    
    int main() {
      const int arrSize = 3;
      int before = 0;
      int nums[ arrSize ] = {97,98,99};
      int after = 1;
    
      // In a moment, you'll add some code here.
    
      printf( "Before is %d = 0.\n", before );
      int i = 0;
      while ( i < arrSize) {
        printf( "nums[%d] is %d.\n", i, nums[i] );
        i = i + 1;
        }
      printf( "After is %d = 1.\n", after );
      }
    
    This program runs as expected. (It has a couple of constructions not seen before. Ask your lab instructor if you're curious about other differences.)

    Now, add the following code at the indicated place:

      // ILLEGAL, OUT-OF-BOUND INDICES!
      nums[ arrSize+2 ] = 17;
      nums[ -1 ] = 19;
    
    What happens this time? What happens if you change arrSize+2 to arrSize+365?

    Exercise:

  • Write a C program which does the equivalent of (build-vector 100 (lambda (i) (* 3 i))), and then print out each element of the array.
  • Write hasNeg as described for the Scheme problem above. Use a while loop. Here is a start for you, showing the syntax of if-statements, and how to pass a vector to a function:
    /* hasNeg:
     * Given a vector v and integer n,
     * return whether or not one of its first n elements is negative.
     * (We assume v has at least n elements.)
     */
    bool hasNeg( int v[], int n ) {
      bool retVal = false;
      int i = 0;
      while ( i < n ) {
        if (...)
          retVal = ...
      }
    
      return retVal;
    }
    
  • Finally, from your function main, here's how you might call hasNeg.
    int main() {
      const int vSize = 10;
      int v[ vSize ];
      // fill v[0]..v[vSize] with values...
    
    
      if (hasNeg( v, vSize ))
        printf( "Some element of v is negative.\n" );
      else
        printf( "No element of v is negative.\n" );
    
      return 0;
      }
    
    If you get stuck, you can look at an answer. It's worth knowing that this function can also be written recursively).

  • Back to Lab 9
    Back to Comp 210 Home