CS365 Final Answers -- Spring 2012


  1. Names, Scopes, and Bindings
    1. Both early and late binding confer certain advantages on a program. One example of early versus late binding is C/C++'s decision to do static linking by placing all libraries in the same object module and Java's decision to do dynamic linking by loading class files on demand at run-time. Describe the advantages that accrue respectively to C/C++'s early binding decision on linking and to Java's late binding decision on linking.

      1. Advantages of early linking: Efficiency--All resolution of function addresses is done statically and function calls are executed using direct jump statements. With dynamic linking, function references are resolved through look-up tables, thus slowing down the execution of the program. Additionally, the code is loaded all at once which is typically faster than loading it piece meal at runtime
      2. Advantages of late linking: Flexibility--Faster startup because the program initially has a smaller footprint. Classes do not have to be loaded if they are unneeded, thus potentially saving memory space. Finally individual classes can be recompiled without having to relink the entire object file, which can be time consuming.

    2. Behold the following program:
      string player1 = "scissors";
      
      string winner(string player2) {
          if (player1 == "rock") {
              if (player2 == "scissors")
                  return player1;
              else if (player2 == "paper")
                  return player2;
              else
                  return "tie";
          }
          else if (player1 == "scissors") {
              if (player2 == "rock")
                  return player2;
              else if (player2 == "paper")
                  return player1;
              else
                  return "tie";
          }
          else { // player1 selected paper
              if (player2 == "rock")
                  return player1;
              else if (player2 == "scissors")
                  return player2;
              else
                  return "tie";
          }
      }
      
      int main() {
        string player1 = "rock";
        string player2 = "paper";
        cout << winner(player2) << endl;
      }
      
      1. What result gets printed by this code under static scoping? Why?

        scissors: the global definition of player1 holds because it is the definition in the most closely enclosing block.

      2. What result gets printed by this code under dynamic scoping? Why?

        paper: the definition of player1 in main holds, because main precedes the global definitions in the list of stack frames. Since player1's value is "rock", player1 loses to "paper".

  2. Data Types and Type-Checking

    1. What is the difference between a statically type checked language and a strongly type checked language?

      A statically typed language is one in which type checking is done at compile-time, while a strongly typed language is one in which the compiler or the run-time system can guarantee that the programs it accepts will execute without type errors. A program can be statically typed and still be weakly typed if it has a lot of exceptions to its type system.

    2. Give one simple code example from C++ that shows that C++ is not strongly typed.

      There are many possible correct answers. Four possible answers are 1) the ability to downcast a pointer from a superclass to a subclass, 2) the ability to use variant records (although in C++ a better design is to use subclasses), 3) arbitrary casts of a piece of memory to any type desired by the programmer (e.g., one can cast a class object to an int with completely unpredictable results), and 4) index out of bounds exceptions in arrays.

    3. Consider the following type declarations:
      struct Date {
         int month;
         int day;
         int year;
      };
      
      struct Time {
         int hours;
         int minutes;
         int seconds;
      };
      
      typedef Date arrivalDate;
      typedef Date departureDate;
      typedef Time elapsedTime;
      
      struct Date admissionDate, graduationDate;
      struct Time startTime;
      arrivalDate HawaiiArrival;
      departure Date HawaiiDeparture;
      elapsedTime runnersTime;
      
      Which of these variables are type equivalent using
      1. structural type equivalence: The variables are all type equivalent because the Date and Type structs have exactly the same structure, with three int fields each.
      2. loose name equivalence: aliased types are considered equivalent and hence admissionDate, graduationDate, HawaiiArrival, and HawaiiDeparture are type equivalent as are startTime and runnersTime.
      3. strict name equivalence: aliased types are considered distinct, and hence only admissionDate and graduationDate are type equivalent.
  3. Event Programming and Graphical User Interfaces

    1. If I want a user to enter a bounded integer between 0 and 20, why is it better to present the user with a slider instead of a textbox?

      The user can immediately see the acceptable choices, which takes advantage of recognition memory as opposed to recall memory, and the user cannot enter an invalid value, either an out-of-range numeric value or a non-numeric value.

    2. For each of the following user interface scenarios, choose from the following list the type of widget you think is most appropriate. One of the problems has two acceptable answers; the others have only one acceptable answer.
      • slider
      • menu/list
      • type in text box
      • radio button
      • check box
      • command button

      1. menu/list You want the user to select an occuption from a set of 50 possible occupations.

      2. slider or radio buttons You want the user to select the number of bedrooms that they are interested in having in a house. The most bedrooms that a house can have is six.

      3. check box You want the user to select a font style. The possible options are bold, italic, underline, and oblique. They can select anywhere from zero to all four options.

      4. type in text box You want the user to enter the weight of a package and are willing to accept any weight up to 10000 pounds.

      5. slider You want the user to specify a color by entering the values for red, green, and blue as integers between 0 and 100. The user should be able to rapidly change the values and see the current color immediately updated in a color swatch.

  4. Model-View-Controller (MVC) model: Behold the following Java pseudo-code for displaying and updating the results of an election:
    class ElectionResults extends JPanel {
        int candidate1voteTotal = 0;
        int candidate2voteTotal = 0;
        int candidate3voteTotal = 0;
        int candidate4voteTotal = 0;
        JFrame window = new JFrame();
        
        void addToCandidate1(int votes) {
            candidate1voteTotal += votes;
            repaint();
        }
    
        void addToCandidate2(int votes) {
            candidate2voteTotal += votes; 
            repaint();
        }
    
        void addToCandidate3(int votes) { ... }
        void addToCandidate4(int votes) { ... }
    
        void initializeView() {
            window.getContentPane().add(this);
        }
        void paintComponent(Graphics g) {
            g.drawText(candidate1voteTotal);
            g.drawText(candidate2voteTotal);
            g.drawText(candidate3voteTotal);
            g.drawText(candidate4voteTotal);
        }
    }
    
    This pseudo-code abstracts away details about making the window visible and where to place the text in a window, so please do not worry about the details. From a broader perspective, the design of this class violates the MVC model. Answer the following questions:

    1. How does this class declaration violate the MVC model?

      It combines the model and the view in a single class

    2. How could the design of this class hinder two programmers that want to add two separate views, one that provides a pie chart of the results and one that provides a scrolling ticker with the results?

      The programmers must both modify the ElectionResults class in some fashion, which means they cannot develop their code independently. At best the programmers will declare new classes for the pie chart and ticekr view and then modify the initializeView method to add the two new views to the window. If they do not coordinate the changes, one set of changes could get lost. In addition, the initializeView method will now be mis-named, because the name clearly suggests that the application only has one view. Of course the method's name could be changed, but that would necessitate even more changes to the code for ElectionResults.

    3. How could you modify this code so that it adheres to the MVC model and supports additional views. Do not write the pseudo-code. Instead answer the following questions:

      1. Which methods/variables would you move out of ElectionResults and where would you move them?

        I would create two new classes, a view class that textually displays the results and a top-level glue class that ties together the model and the views. The initializeView method and the window variable would get moved to the glue class and the paintComponent method would get moved to the textual view class.

      2. Currently you have two general sets of methods for this class--mutator methods and drawing methods. What additional general sets of methods would you need to add to either the existing class or your newly created class(s) to get a clean separation beween their implementations and to allow them to communicate?

        You should add the following three sets of methods:

        1. registration methods to the model (ElectionResults) that allows views to register with it,
        2. notification methods to the views that can be called by the model when the candidates' vote tallies change, and
        3. accessor methods to the model (ElectionResults) so that views access the state information of the model indirectly through methods rather than directly through instance variables.

  5. Consider the following Java class definition:
    public class BadThreads {
    
        static String message;
    
        private static class CorrectorThread
            extends Thread {
    
            public void run() {
                try {
                    sleep(1000); 
                } catch (InterruptedException e) {}
                // Key statement 1:
                message = "Mares do eat oats."; 
            }
        }
    
        public static void main(String args[])
            throws InterruptedException {
    
            CorrectorThread t = new CorrectorThread();
            t.start();
            message = "Mares do not eat oats.";
            Thread.sleep(2000);
            // Key statement 2:
            System.out.println(message);
        }
    }
    
    The application should print out "Mares do eat oats." It will do so most of the time, but not all of the time, because there is a happens-before relationship between key statement 1 and key statement 2 that is not always obeyed. Sometimes key statement 1 will not be executed before key statement 2 and sometimes key statement 1 will be executed before key statement 2 but its result will not be visible to the main thread.

    1. How is it possible that key statement 1 could execute before key statement 2 and yet the change to message not be visible to the main thread?

      Threads keep their own cached copies of variables and if the corrector thread still exists when the main thread tries to print the message, then the change to message may not be visible because either the corrector thread may not yet have written it out to main memory, or alternatively, the main thread may not have loaded the changed value in from main memory.

    2. Java provides at least two different strategies that can be used to ensure that key statement 1 gets executed before key statement 2. Name one of these strategies, along with a quick explanation of why/how the strategy ensures the proper sequencing.

      1. The main thread can call join on the corrector thread. This will cause the main thread to wait until the corrector thread has finished. When the corrector thread finishes, it must flush its copy of message to main memory.
      2. The main thread can call wait() and the corrector thread can call notifyAll() when it has changed message. This would require locking the BadThreads object. It works because mail will wait until the corrector thread has modified message and called notifyAll(). This should also cause the change to message to be flushed to main memory.

    3. Suppose you are confident that the sequencing will occur in the right order and want a lighter weight strategy that simply ensures that the change to message will be visible to the main thread. Java provides at least two different strategies that can be used to ensure that the results of key statement 1 will be made visible to the main thread. Name one of these strategies, along with a quick explanation of why/how the strategy ensures the proper sequencing.

    1. It could use volatile variables: This will force the corrector thread to flush its copy of message to main memory after it makes the change to message.

    2. It could encapsulate message in an object with synchronized methods and never reference message except through those methods. Synchronized methods flush all of an object's instance variables to main memory immediately before exiting, and force all of the thread's variables to be read from main memory on entry to the method. Hence the change to message will be written to main memory when the setting method exits in the corrector thread and will be read from main memory when the accessor method starts in the main thread.

  6. Functional Programming

    1. Name two essential elements of pure functional programming that differentiate it from imperative programming.

      1. Defines the outputs of a program as a mathematical function of the input
      2. Lack of side-effects

    2. What recursive programming technique can a programmer use to help a functional language compiler improve the efficiency of the compiled code? Explain how the compiler can take advantage of this technique to produce more efficient code.

      Tail Recursion: Because a tail recursive function performs no further computation, it does not need to retain any state information. Hence the compiler can re-use the stack frame by inserting a branch statement back to the top of the subroutine rather than having to recursively make function calls and add an indeterminate number of frames to the stack.

    3. Using Scheme, write a function named split that takes a list as a parameter, splits the list in two, and returns the two split lists as a cons pair. If the list has an odd number of elements, place the additional element in the first split list. Your answer may not use either iteration or side-effects. Hint: There is not a Scheme operator for splitting a list in the middle, so your function should recursively assign the first two elements of the list to the lists split lists you are creating, and then recurse on the remainder of the list. I do not care whether or not you use tail recursion. Here are two possible answers:

      1. (define (split listToSplit) (letrec ((splitHelper (lambda (listToSplit list1 list2) (cond ; if the remaining list is empty, reverse each ; of the lists and return them ((null? listToSplit) (cons (reverse list1) (reverse list2))) ; if the remaining list has a single element, add ; it to the first list, then reverse the two lists ; and return them ((null? (cdr listToSplit)) (let ((newlist1 (cons (first listToSplit) list1))) (cons (reverse newlist1) (reverse list2)))) ; if the remaining list has 2 or more elements, assign ; the first element to list1, the second element to ; list2, and then recursively split the remaining part ; of the list (else (splitHelper (cddr listToSplit) (cons (first listToSplit) list1) (cons (second listToSplit) list2))) )))) (splitHelper listToSplit '() '())))

      2. ;; adds an item to the first list, then swaps lists in the recursive ;; call so that the second list gets the next item (define (split listToSplit) (letrec ((splitHelper (lambda (listToSplit list1 list2) (cond ; the second list always contains the longer list if ; the original list contains an odd number of elements, ; so we must return them in the order (list2 . list1) ; to satisfy the constraints of the problem ((null? listToSplit) (cons (reverse list2) (reverse list1))) ; swap the order of the lists so that list2 gets ; the next element from the list (else (splitHelper (cdr listToSplit) list2 (cons (first listToSplit) list1) )))))) (splitHelper listToSplit '() '())))