CS140 Midterm 1 Solutions

Fall 2018


  1. (15 points) Declare a class named PgmImage that you could place in a .h file. Do not write the method definitions as these would not go in a .h file. PgmImage should have the following variables and methods:

    1. picture: a 2D vector of ints
    2. photographer: a string containing the name of the photographer who took this picture.
    3. printPicture: a void method that takes a string parameter named filename and writes the picture to that file.
    4. getPhotographer: a method that takes no parameters and that returns the photographer's name.
    5. crop: a void method that crops the picture. This method takes the following parameters:
      • croppedImage: a 2D vector of ints. The cropped image will be placed in this 2D vector
      • row, col, numRows, numCols: 4 ints specifying the cropped rectangle. The cropped rectangle starts at (row, col) in the picture and includes numRows rows and numCols columns.
      class PgmImage {
      public:
        void printPicture(string filename);
        string getPhotographer;
        void crop(vector<vector<int> > &croppedImage, int num, int col, int numRows, int numCols);
      protected:
        vector<vector<int> > picture;
        string photographer;
      };
      

  2. (12 points) Suppose I want to declare a class named PgmImage and use it in a program named ImageManipulator.cpp. I also want to implement the methods for PgmImage in a separate file from ImageManipulator.cpp. Answer the following questions:

    1. If the class declaration for PgmImage is placed in a file named PgmImage.h, then what statement would you write to include this header file in .cpp files?

      PgmImage.h: The class declaration is placed in a header file with the name of the class and a .h suffix. The .h suffix indicates that the file is a header file.

    2. In what file should the method definitions for PgmImage be placed? Give the name of the file, following the naming conventions discussed in class.

      PgmImage.cpp: The method definitions are placed in their own .cpp file with the name of the class as the name of the file.

    3. Using a single g++ statement, how could I compile all of these files into an executable named ImageManipulator?

      g++ -o ImageManipulator ImageManipulator.cpp PgmImage.cpp

    4. Suppose instead that I wanted to separately compile these files and then link together their object files into an executable named ImageManipulator. Write the set of statements that would be required to perform this separate compilation and then linking into the executable.
      g++ -c ImageManipulator.cpp
      g++ -c PgmImage.cpp
      g++ -o ImageManipulator ImageManipulator.o PgmImage.o
      
    5. Suppose that I execute ImageManipulator from the command line by writing the following command:
      ./ImageManipulator < bvz.pgm
            
      What does the < operator cause the C++ program to do?

      It causes cin to read input from bvz.pgm rather than from the console. Another way of putting it is that it causes stdin to be re-directed from the console to bvz.pgm.

  3. (8 points) In a function, why should you use reference parameters for objects (e.g., strings, vectors, instances of classes), rather than value parameters, even if you do not intend to modify the object? For example, if you want to pass a vector object to a function, you should declare the vector parameter as follows:
    void mystery(string &vector) { ... }
    
    Straight from the procedures notes: You want the convenience of using a procedure, but you don't want to make copies, because that is expensive.

    Here is a slightly wordier explanation: A reference parameter only copies a pointer to an object, which is only 4-8 bytes, whereas a value parameter would copy the contents of the entire object. If the object is quite big, such as a lengthy string or a vector with a large number of elements, then copying the contents of the entire object could slow down the program considerably.

  4. (10 points) Answer the following two questions about typedefs:

    1. Write a typedef statement that declares a new type named Pixels to be a vector of ints.

      typedef vector<int> Pixels;

    2. Declare picture to be a vector of Pixels.

      vector<Pixels> picture;

  5. (24 points)

    1. Insert the keys 22, 28, 39, 48, 41, and 61 into the following two hash tables using a) separate chaining, and b) quadratic probing. For separate chaining, use commas to separate the keys that would go into an entry. For example, if the keys 22, 28, and 39 all went into the same entry, you would write (22, 28, 39) in that entry. You should use the hash function:
      h(key) = key % 13
      
       Separate ChainingQuadratic Probing
      03939
      1  
      228, 4128
      3 41
      4  
      5 61
      6  
      7  
      8  
      922, 48, 6122
      10 48
      11  
      12  

    2. For quadratic probing please also complete the following table showing the comma, separated list of entries you would probe in order to insert each key (e.g., if you examined entries 6, 7, and 11 in order to insert 48, then you would write (6, 7, 11) next to 48 in the below table--this is only an example--it is not the actual set of entries you will probe for 48). This will allow us to assign you partial credit if you do not get the entries correct in the quadratic probing hash table.

      keyquadratic probing
      229
      282
      390
      489, 10
      412, 3
      619, 10, 0, 5
      Separate chaining
      1. 22: 22%13 = 9 so 22 is added to the vector associated with entry 9
      2. 28: 28%13 = 2 so 28 is added to the vector assocaited with entry 2
      3. 39: 39%13 = 0 so 39 is added to the vector assocaited with entry 0
      4. 48: 48%13 = 9 so 48 is added to the vector assocaited with entry 9
      5. 41: 41%13 = 2 so 41 is added to the vector assocaited with entry 2
      6. 61: 61%13 = 9 so 61 is added to the vector assocaited with entry 9

      Quadratic Probing:

      1. 22: 22%13 = 9. Entry 9 is empty so we place 22 in entry 9.
      2. 28: 28%13 = 2. Entry 2 is empty so we place 28 in entry 2.
      3. 39: 39%13 = 0. Entry 0 is empty so we place 39 in entry 0.
      4. 48: 48%13 = 9. Entry 9 is full. We next query the following entries:
        1. 10: hash(48) = (9 + 12) % 13 = 10
        Entry 10 is empty so we place 48 in entry 10.
      5. 41: 41%13 = 2. Entry 2 is full. We next query the following entries:
        1. 3: hash(41) = (2 + 12) % 13 = 3
      6. 61: 61%13 = 9. Entry 9 is full. We next query the following entries:
        1. 10: hash(61) = (9 + 12) % 13 = 10
        2. 0: hash(61) = (9 + 22) % 13 = 13 % 13 = 0
        3. 5: hash(61) = (9 + 32) % 13 = 18 % 13 = 5

  6. (12 points) The intent of the following code fragment is to read a series of name/salary pairs from stdin and to print the average salary to stdout.
    1   string name;
    2   int salary;
    3   int salarySum = 0;
    
    4   while (!cin.eof()) {
    5       cin >> name >> salary;
    6       salarySum += salary;
    7   }
    8   cout << "total salary = " << salarySum << endl;   
    							
    1. Show what the code fragment prints if the input is:
      	  Suzy 100
      	  Frank 80
      	  Ebber 50
      	
      total salary = 280
        
    2. What is wrong with the code that causes the output to be incorrect?

      cin.eof() is reactive, not proactive. That means that when the last name/salary pair is read, cin.eof() will still return false and the loop will execute one too many times. The cin on line 5 will fail and leave name and salary unchanged, which will result in the last salary being added to salarySum twice.

    3. Modify the incorrect portion of the code to make it correct. Please do not re-gurgitate the entire code. Only modify the incorrect portion.

      The correction is to replace the condition !cin.eof() with the condition cin >> name >> salary and to delete line 5:

      while (cin >> name >> salary) {
        salarySum += salary;
      }
      

  7. (8 points) The following code fragment is supposed to read names into a vector.
    1    vector<string> names;
    2    int i = 0;
    3    string name;
    
    4    while (cin >> name) {
    5	names[i] = name;
    6       i++;				   
    7    }
    				   
    1. What is wrong with the code? (you may use no more than 4 sentences)

      The names vector has size 0 so when you try to assign the variable name to names[i], the code will seg fault because entry i does not exist.

    2. Change the incorrect portion of the code to make it correct. Do not regurgitate the entire code. Only modify the incorrect code fragment.

      The solution is to replace the assignment statement with a call to push_back, which will allocate a new entry in the vector and then assign name to that new entry. You should also delete the i++ statement since there is no longer a need to keep track of which entry you are assigning name to:

      while (cin >> name) {
        names.push_back(name);
      }
      
      Technically you should also delete the declaration for i since you no longer need it, but I did not deduct points for failing to do so.

  8. (12 points) Please read the following instructions before attempting this problem:

    1. 0xB5 = 1011 0101

    2. (0101 1101) ^ (1110 1100) = 1011 0001 = 0xB1

      ^ is bitwise xor

    3. (0010 0101) | (1010 1100) = 1010 1101 = 0xAD

      | is bitwise or

    4. (1010 1100) << 3 = 0110 0000 = 0x60

      << left shifts the bits by 3 positions, which means that the leading 101 on the left side gets thrown into the bit bucket and three 0's come in on the right side.

  9. (5 points) g What is the output of the following printf statement:
    char value = 'a' + 6;
    printf("%c", value);
    
    6 is being added to the ascii code for 'a', which is 97. The sum is 103, which is the ascii code for 'g'. In plain English, we are counting 6 characters from 'a',which gives you:
    1 2 3 4 5 6
    b c d e f g
    

  10. (14 points) Suppose you are given the following variable declarations:
    string runner;
    int minutes, seconds;
    
    Write a printf statement that:

    1. outputs runner in a left-justified field of 15 characters, and time in the following two formats: a) as a right-justified floating point number in a field that is minimally 6 characters wide with 1 decimal digit where the whole number is the minutes and the fractional number is the seconds divided by 60, and b) as a right-justified string in the format mm:ss (i.e., both minutes and seconds should be in fields that are 2 characters wide).
    2. If the number of seconds is less than 10, pads the seconds output with a single 0, and
    3. Puts a single space between each of the three fields (but no space at the end of the output).
    For example, if the runner's name is "Bradley", minutes is 5, and seconds is 40, then the output would be:
    Bradley            5.7  5:40
    
    (40/60 = .67 which when rounded to one digit is .7) and if seconds were instead 4, then the output would be:
    Bradley            5.1  5:04
    
    printf("%-15s %6.1f %2d:%02d\n", runner.c_str(), 
                   minutes + (double)seconds/60, minutes, seconds);
    

  11. (Extra Credit-12 points) You have written a program that reads the exam scores for a student and for each student prints the student's name and exam average. Your program reads lines of input where the first and second words on the line are a student's first and last name and the remaining portion of the line are scores for the student. For example:
    Donald Duck 20 40 50
        
    All exam scores must be integers between 0 and 100 and every student must have at least one exam score. A line of input must therefore have at least 3 inputs--a firstname, lastname, and test score. When you test your program, you need to come up with a series of test cases for correct input, incorrect input, and boundary cases. List all the test cases you should provide in order to thoroughly test your program (this is what you are supposed to do in each of your labs). I will get you started with a couple of example test cases:

    Non-erroneous input: This will include all "normal" data plus all boundary cases.

    1. A line with a single test score--this represents the minimum possible number of test scores
    2. A test score of 0. This is the smallest valid test score.
    3. A test score of 100. This is the largest valid test score.

    Erroneous input: A test case with:

    1. An exam score that is negative.
    2. An exam score that is greater than 100.
    3. An exam score that is not an integer.