CS 176, Introduction to Computer Science II

Summer 2001 - Design Test 1 Answer


It's usually a good strategy to start answering questions at the places that have the most information, which, in this problem, means start with the prototypes because the problem describes them almost completely:

  1. seasons accepts no arguments and returns the integer number of breeding seasons - that translates directly into the prototype
    int seasons(void)
    

  2. population accepts as input an integer i representing the number of breeding seasons since the start of the simulation and returns the integer number of jackrabbits - another direct translation, this time into
    int population(int season)
    
There may be other procedures, and prototypes, necessary, but for now these two will do.

The problem also gives much information on how seasons() and population() should behave when called, so dealing with how these two procedures should be implemented seems like a good question to take up next.

seasons() returns the number of breeding seasons until the jackrabbit population is not less than max. The important thing to note about seasons() is that it can be implemented in terms of population():

seasons():

  set s to zero

  while population(s) is less than max
    increase s by one

  return s
population() returns the number of jackrabbits at the end of breeding season i. population()'s implementation follows directly from Professor Fibonacci's rules for population growth:
population(season):

  // Sanity check - seasons shouldn't be negative.

     if season less than 0
       return 0

  // Initially the rabbits haven't bread yet, so the population is equal to
  // start. 

     if season equals 0
       return start

  // The population after the first season is determined by rule 1.

     p1 = start
     p2 = start + 1

  // The population for seasons after the first requires that we remember the
  // population of the previous two seasons and loop around through the seasons
  // as described by rule 2.

     i = 1

     while i less than season and p2 less than max
       j = p1
       p1 = p2
       p2 = j + p2
       i = i + 1

  // If there are too many rabbits, cut them back to the maximum.

     if p2 greater than max
       p2 = max

  // Return the final population.

     return p2
Next comes the data structures. From the design of population() and seasons(), it seems clear start and max are necessary:
  int start, max
Because the Professor wants to be able to run several ecosystem models at the same time, these variables can't be global variables because it is unclear how the global variables would be shared among the models.

The solution, of course, is to wrap everything in a class:

class jackrabbit_ecosystem {

  public:

    jackrabbit_ecosystem(int s, int m)
    int seasons(void)
    int population(int season)

  private:

    int max, start
  }
The data structures max and start should not be accessible outside the class because changing them during a simulation messes up the results returned by seasons() and population().

The ecosystem class also has to be initialized, and the easiest way to do that is to create a constructor that accepts the initial values for max and start. The prototype for the constructor is shown above, and its implementation simply assigns s to start and m to max:

jackrabbit_ecosystem():

  // Sanity check.

     if s > m
       abort()

     max = m
     start = s
It's also usually a good strategy to examine your answer once you have it to see if it is correct and is, more generally, a good answer.

One thing to note about the jackrabbit ecosystem given above is that it does a lot of computation. For large values of season, population() can do a lot of looping. In addition, seasons() is implemented by calling population() repeatedly; this might make the jackrabbit ecosystem slow to execute. Also, population() seems complicated, with all those tests and computations going on at each call.

Can this design be improved by reducing computations and making population() simpler? The key to answering this question is to note that, once start and max are fixed, the values computed by population() for a specific season don't change; that is, for a specific ecosystem e and season s, the value of e.population(s) is constant. This suggests that the population values be computed exactly once and stored someplace so they can be used by population().

Where should the populations be stored? What we want is something that, when given a season (an integer) returns the population for that season (another integer). A little though concludes that an array of integers fills the bill nicely. An array index i represents season i and the array element pop[i] represents the population at season i.

How big should the population array be? Here the problem assumption that max - start <= 1000 helps us: given the definition of population growth, there can never be more than 1000 numbers to store, so a thousand-element integer array is big enough.

How should the population array be filled? We can pretty much steal the code from population(), but there is a subtle point involving the maximum population to be dealt with. In particular, how should computed populations greater than the maximum be dealt with in the population array? One possibility is to fill in the extra spaces in the array with the maximum population rather than the computed population. This works but is somewhat wasteful in computation and array space. An alternative is to stop computing when the population reaches or exceeds max and remember the season in which that occurred. This alternative allows us to stop computing once the population grows too big, and also simplifies greatly the implementation of seasons(), which now only needs to return the maximum season value.

Finally, where should the population computation be done? Because it only needs to be done once and needs to be done before the ecosystem can be used, putting it in the constructor seems like a good idea.

Based on these observations, the redesigned jackrabbit-ecosystem class is

class jackrabbit_ecosystem {

  public:

    jackrabbit_ecosystem(int s, int m)

      pop[0] = s
      pop[1] = s + 1
      if pop[1] > m
        pop[1] = m

      max_season = 1
      max_pop = m
      while pop[max_season] < max_pop
        max_season = max_season + 1
        pop[max_season] = pop[max_season - 2] + pop[max_season - 1]

    int seasons (void)
      return max_season

    int population (int season)
      if 0 <= season and season < max_season
        return pop[season]
      else
        return 0

  private:
 
    int max_pop
    int max_season
    int pop[1000]
  }
Now all the member functions (but not the constructor) are simpler than their counterparts in the previous design. Notice also how both classes have the same appearance from the outside, even though they have significantly different designs.


This page last modified on 13 June 2001.