1.

For this question, you are going to be working with a list of objects. The objects represent Very Large Numbers, but you do not need to be concerned with how they are implemented. The class declaration is given below, you do NOT need to define any part of the number class. The declarations of the member variables have been left out; do not be concerned with them.

Every object of type number occupies 75 bytes of memory.

 

class number

{ public:

    number(void);

    number(string s);

    void add(number x);

    void subtract(number x);

    void multiply(number x);

    void divide(number x);

    int compare(number x);

    void print(void); };

 

Using this class definition, if a and b are numbers, then a.add(b) performs the operation a+=b; and all the arithmetical operations work in the same way. a.compare(b) returns –1 if a is less than b, 0 if a is equal to b, and +1 if a is greater than b. The first constructor takes a string containing the normal decimal representation of an integer (e.g. "347821634") and makes a number from it.

 

Here are two declarations:

number AA[100];

number *BB[100];

 

                Say, in plain English, exactly what each declaration declares, and how much memory each will occupy. List and explain the relative advantages and disadvantages (if any) of each declaration in terms of memory use, speed of execution, and convenience for programming.

 

AA is an array of 100 number objects, and occupies 7500 bytes.

BB is an array of 100 pointers to number objects, and under the safe assumption that pointers require 4 bytes, occupies 400 bytes.

 

If it is not known how many objects will be required, an array must be created at the maximum possible size, which can involve a lot of wasted memory. Using BB only wastes 4 bytes for each entry that turns out to be unnecessary, whereas AA wastes 75 bytes for each.

 

If putting things into the array, or moving array entries around, is to be a common operation then BB is faster, because only 4 bytes needs to be moved each time. If accessing components of the objects within the array is to be a common operation then AA is faster, because following a pointer does take some time.

 

AA requires the existence of a default constructor for number, and some mechanism for determining whether or not an array entry is in use. With BB, unused entries can be set to NULL.

 

The next part requires you to write three functions that work with the second declaration (BB). Think about all three before you start the first, as the design of each depends upon the designs of all. Your three functions must work together properly.

 

I would choose a design which uses an extra int variable to record how many entries in the array are in use. That way I would not need to spend any time setting all the entries of a potentially enormous array to NULL. Such a design would require me to ensure that no gaps are left in the array, so after removal an element will have to be repositioned to fill the gap left.

 

This design decision means that the result of find is in some way unstable. If I find a number, then remove another number, it might be that the first number found gets relocated as a result of the removal, so the position I’ve got for it becomes invalid. If this is unacceptable, a different design would be required.

 

            Write three functions, one to add a number to the list stored in BB, one to search for a particular number in the list BB, and one to remove a particular number from BB. The prototypes for the functions are:

int addtolist(number *n);  // returns 1 normally but 0 if it fails for some reason

int find(number *n);       // returns the position in the list, or –1 if not found

int remove(number *n);     // returns 1 normally, or 0 if it fails.

You may add extra functions or variables if necessary.

 

int num=0;  // the number of entries in use.

const int max=100; // the maximum capacity.

 

int addtolist(number *n)

{ if (num>=max) return 0;  // this is the only possible cause of failure

  BB[num]=n;

  Num+=1; }

 

int find(number *n)

{ for (int i=0; i<num; i+=1)

    if (BB[i]->compare(*n)==0)

      return i;

  return -1; }

 

In the find function, notice that the normal comparison (BB[i]==n) is not good enough; it just compares the two pointers. We need to compare the actual number objects. It is possible that two number objects with different addresses in memory will contain the same number.

 

int remove(number *n)

{ int i=find(n);   // no need to repeat the work

  if (i==-1) return 0;  // only reason for failure to remove is absence from list

  num-=1;

  BB[i]=BB[num]; }  // move last number down to fill the gap

 

            Finally, write a function with the following prototype:

int addinputtolist(void);

it waits for the user to type a number, and then adds it to the list BB, returning 1 for success, or 0 for failure.

 

The only useful constructor we have for a number is the one that takes a string parameter, so we have to read a string from the user, then convert it to a number. There are three very similar and equally correct ways of doing this. All three methods use “new number” to create the new number in permanent memory, so that it will still be valid when the function exits. All three functions also use the already defined function addtolist to avoid unnecessary duplication of effort and additional chances of error.

 

// using iostream

int addinputtolist(void)

{ string s;

  cin >> s;

  return addtolist(new number(s)); }

 

// using stdio

int addinputtolist(void)

{ char s[1000];

  scanf(“%s”, s);

  return addtolist(new number(s)); }

 

// using stdio in a probably more sensible way

int addinputtolist(void)

{ char s[1000];

  fgets(s, 999, stdin);

  int n=strlen(s);

  if (s[n-1]==’\n’) s[n-1]=’\0’;

  return addtolist(new number(s)); }

 

2.

A Queue is a special object for holding data. It behaves rather like an array, except that you are only allowed to access the data it contains in a very restricted way. Apart from constructors, there are usually only three simple access methods for a queue:

1.        A checking method: is_empty(); returns 1 if the queue has no data in it, 0 otherwise.

2.        An insertion method: insert(something *); adds the given object to the queue's collection of data.

3.        A removal method: something *remove(void); removes an object from the queue, and returns that object as its result.

Notice that the only way to get information from the queue also involves permanently removing that piece of information, and there is no way to say which piece of information you want. That restriction is fundamental to queues.

The simple rule is that objects are removed in exactly the order they were inserted in. If you insert A then insert B then insert C, then remove something, you will get A back. If you then insert D then remove something, you get B back. If you then remove another thing you get C, and removing another thing gets D, after which the queue is empty again. Insertions and removals can be done at any time; a removal always gets the object that has been in the queue for longest.

An alternative, and perhaps better, way of thinking about a queue is to say that a queue has two ends, the front and the back. Objects can only be added to the back of a queue, and can only be removed from the front.

 

        objects go in here                               objects come out here

                           

 

                Implement a queue object (define the class and all appropriate constructors and methods) suitable for holding a queue of person objects. Do not worry about defining person, just assume that class already exists. Although the insert and remove methods may be used millions of times, you are assured that there will never be more than 100 objects in your queue at the same time (i.e. if insert has been used 1,000,000 times, you can be sure that remove has been used at least 999,900 times).

 

First, notice that you aren’t expected to know anything about queues in order to answer this question. Everything you need is given above.

 

An initial incorrect design will give the general idea.

 

We have to be able to access both ends (front and back) of the queue, and both ends can move. Therefore we should have two counters, one telling us where the front is, and one telling us where the end is. A third variable counting how many entries are in the list also simplifies this.

 

const int size=100;

 

class Queue

{ protected:

    int front, back, num;

    Person *data[size];

  public:

    Queue(void);

    int is_empty(void);

    void insert(Person *p);

    Person *remove(void); };

 

Queue::Queue(void)

{ front=0;

  back=0;

  num=0; }

 

int Queue::is_empty(void)

{ return num==0; }     // see how useful the third variable is?

 

void Queue::insert(Person *p)

{ if (num>=size) error(“insert when the queue is full”);

         // insertions happen at the back:

  if (back>=size) error(“oh dear”);

  data[back]=p;

  back+=1;

  num+=1; }

 

Person *Queue::remove(void)

{ if (num==0) error(“remove when the queue is empty”);

       // removals happen at the front:

  if (front>=size) error(“oh dear”);

  Person *answer=data[front];

  front+=1;

  num-=1;

  return answer; }

 

The only problem with this solution is pointer out by the two “oh dear” errors. If we insert a Person into the queue, then immediately remove it, both counters, front and back, will have been increased by one, even though the queue remains empty. If this procedure is repeated 100 times, we will run out of space in the array, when the queue is empty of data.

 

One possibility is that every time a Person is removed from the queue, all remaining persons are moved up by one position, so that the value of front is always zero (of course we wouldn’t bother with the variable in that case). This works, but is terribly inefficient. In a large queue, all that moving could take more time than all the rest of the program.

 

There is a better method. Whenever front or back reach 100, reset them to zero (so the positions are really stored modulo size). So long as the back of the queue doesn’t catch up with the front (this is guaranteed not to happen if we keep track of num and never let the queue get full), we will never have a problem.

 

Under this scheme, insert and remove are changed as follows:

 

void Queue::insert(Person *p)

{ if (num>=size) error(“insert when the queue is full”);

         // insertions happen at the back:

  data[back]=p;

  back+=1;

  if (back==size) back=0;

  num+=1; }

 

Person *Queue::remove(void)

{ if (num==0) error(“remove when the queue is empty”);

       // removals happen at the front:

  Person *answer=data[front];

  front+=1;

  if (front==size) front=0;

  num-=1;

  return answer; }

 

                How would you change your definitions if there is no limit to the number of objects that may be in the queue at the same time? You do not need to re-write the whole answer, just indicate the changes that would be made.

 

This clearly requests a growable or resizable array. For this to work, data needs to be a pointer to an array instead of a direct array, and the constructor becomes responsible for creating the array. Also, size must become a variable in order to correctly record the current size.

 

As we are planning on changing the size of the array at some time, it would seem sensible to provide a method for doing this. I’ll make it protected so that users can’t play around with it.

 

class Queue

{ protected:

    int front, back, num, size;

    Person **data;

    void resize(int newsize);

  public:

    Queue(void);

    int is_empty(void);

    void insert(Person *p);

    Person *remove(void); };

 

Queue::Queue(void)

{ front=0;

  back=0;

  num=0;

  size=100;

  data = new (Person *) [size]; }

 

int Queue::is_empty(void)

{ return num==0; }

 

The insert method is only different when the array needs to grow, and the remove method doesn’t need to change at all. Notice how little the whole thing has had to change.

 

void Queue::insert(Person *p)

{ if (num>=size)

    resize(size+100);

  data[back]=p;

  back+=1;

  if (back==size) back=0;

  num+=1; }

 

Person *Queue::remove(void)

{ if (num==0) error(“remove when the queue is empty”);

       // removals happen at the front:

  Person *answer=data[front];

  front+=1;

  if (front==size) front=0;

  num-=1;

  return answer; }

 

Now, the only major change is the addition or the resize method. Resize must create a new array of the appropriate size, and copy all the Person pointers into it. This would be an ideal time to tidy things up and make everything start from position zero again. It isn’t necessary to do this tidying up, but it certainly simplifies the procedure:

 

void Queue::resize(int newsize)

{ if (newsize<size) error(“somebody has messed up”);

  Person **newdata = new (Person *) [newsize];

  int oldpos=back, newpos=0;

  for (int i=0; i<num; i+=1)

  { newdata[newpos] = data[oldpos];

    newpos+=1;

    oldpos+=1;

    if (oldpos>=size) oldpos=0; }

  delete[] data;

  data=newdata;

  size=newsize;

  back=newpos;

  front=0; }

 

A little complex perhaps, but it was an extra-credit part.

 

3.

                A Dog is a special mammal for fetching sticks and eating things. It has a mouth at one end, a tail at the other end, and a leg at each corner. Draw a picture of a dog.