Advanced Programming II, Spring 2001

Programming Assignment 5 - An Example Solution


Table of Contents

The Problem

This problem is an example of a large class of problems related to rectangle geometry. Even problems that don't explicitly involve rectangles can make some use of them, usually in the form of quick estimates based on bounding-box rectangles (think of the previous assignment).

I learned the importance of cycles in these kinds of problems while I was on a job interview at Bell Labs in Homdel. I was interviewing with a group working on something like VideoText, where I saw a demo showing how they could get hidden-rectangle elimination for free by drawing the rectangles in order of increasing level. I asked them what would happen if they tried to draw three simutaniously overlapping rectangles. They didn't know and the demo was canned, so we couldn't try it.

The Algorithm

The key to finding a good algorithm for this problem is recognizing that there's two kinds of levels involved. The problem requires that the rectangles be printed in order of their exact levels. However, when sliding rectangles around, it's not necessary to know their exact levels; it's only necessary to know that one rectangle is above another. Using the approximate level (above or not) greatly simplifies the algorithm.

A data structure for representing approximate levels needs to keep track of which rectangles are above others. This is easily done by, for example, storing rectangles in a sequence such that a rectangle earlier in the sequence either does not overlap or ovalaps above a rectangle later in the sequence.

A data structure for representing exact levels is more expensive than the approximate-level data structure, either in terms of time or space (or both). A rectangle's exact level depends on all the rectangles undeneath it; the data structure would need to either keep track of that information or else recompute it whenever rectangles move. In addition, a rectangle's exact level may have influence on the exact level of any rectangle that overlaps above it; this information too has to be kept or computed. In either case, the exact-level data structure requires either extensive cross linkage information among rectangles or expensive global overlap computations.

The Program

The program repeatedly reads a line and either creates a new rectangle or slides an old one depending on what was read. When there's no more input, print the results and exit.
<main()>= (U->)

int main(void) {

  rectangle_list rectangles;
  int x1, y1, x2, y2;
  read_returns rr;

  while ((rr = read_data(cin, x1, y1, x2, y2)) != eof_read)
    if (rr == rectangle_read) {
      rectangle r(x1, y1, x2, y2);
      rectangles.insert(r);
      }
    else if (rr == slide_read)
      rectangles.slide(x1, y1, x2, y2);
    else {
      cerr << "Unknown read_data() return value in main().\n";
      return EXIT_FAILURE;
      }

  rectangles.print(cout);

  return EXIT_SUCCESS;
  }

Defines main (links are to index).

<main.cc>=

#include <iostream>
#include <stdlib.h>
#include "rectangle.h"
#include "rect-list.h"
#include "read.h"

<main()>
Defines main.cc (links are to index).

Input

Input is a specification for either a rectangle or a slide, both of which require four numbers. The only publically interesting read routine is the one that returns an input spec of either type.
<read.h>=

#ifndef _read_h_defined_
#define _read_h_defined_

#include <iostream>

enum read_returns {
  rectangle_read,
  slide_read,
  eof_read
  };

extern read_returns read_data(
  istream & in, int & x1, int & y1, int & x2, int & y2);

#endif
Defines read.h, read_returns (links are to index).

read_data() reads the next rectangle or move from the input stream in and, assuming there's something there, returns the four integers read in x1, y1, x2, and y2; the return value indicates what was read.

Using a string to read the x or -> requires a bit of care. Doing something like

int x1, y1, x2, y2;
string s;
cin >> x1 >> y1 >> s >> x2 >> y2;
fails when given the correctly formatted input 1 1 ->2 2 because the string s is assigned "->2".
<read an input spec>= (U->)

read_returns read_data(istream & in, int & x1, int & y1, int & x2, int & y2) {

  if (!read_int_pair(in, x1, y1)) return eof_read;
  
  char c;
  if (!(in >> c)) badeof("an arrow or an x");

  if (c == '-') {
    if (!(in.get(c))) badeof("an arrow");
    if (c != '>') badc(c, "an arrow");
    }
  else if (tolower(c) != 'x')
    badc(c, "an arrow or an x");

  if (!read_int_pair(in, x2, y2))
    badeof("the second pair of integers");

  return c == '>' ? slide_read : rectangle_read;
  }
Defines read_data (links are to index).

read_int_pair() reads a pair of integers from the input stream in, storing them in x and y. Return true if a pair was read, false otherwise; die on an error.

<read an integer pair>= (U->)

static bool read_int_pair(istream & in, int & x, int & y) {

  if (!read_an_int(in, x)) return false;
  if (!read_an_int(in, y)) badeof("the second of two integers");
  return true;
  }
Defines read_int_pair (links are to index).

read_an_int() reads an integer from the input stream in and stores it in i, returning true if an integer was read and false otherwise. It dies on an error.

<read an integer>= (U->)

static bool read_an_int(istream & in, int & i) {

  if (cin >> i) return true;
  if (cin.eof()) return false;

  if (cin.fail()) {
    char c;
    if (!(cin >> c)) c = '?';
    badc(c, "an integer");
    }

  cerr << "Unrecoverable input error during an integer read.\n";
  exit(EXIT_FAILURE);

  return false; 
  }
Defines read_an_int (links are to index).

A couple of helper routines to print an informative error message about incorrect input.

<input-error routines>= (U->)

static void badc(char c, const string & what) {
  cerr << "Unexpected character '" << c << "' while reading " << what << ".\n";
  exit(EXIT_FAILURE);
  }

static void badeof(const string & what) {
  cerr << "Unexpected end-of-file while reading " << what << ".\n";
  exit(EXIT_FAILURE);
  }
Defines badc, badeof (links are to index).

<read.cc>=

#include <string>
#include "read.h"

<input-error routines>

<read an integer>

<read an integer pair>

<read an input spec>
Defines read.cc (links are to index).

The Rectangle List

The public operations supported by the rectangle list are directly related to the operations that can be performed on rectangles:

  1. Create an empty rectangle list.

  2. Insert rectangles into the list.

  3. Print the rectangles in the list in order of increasing level.

  4. Slide a rectangle in the list.
<rectangle list public routines>= (U->)

rectangle_list();
void insert(rectangle & r);
void print(ostream & out);
void slide(int x, int y, int delta_x, int delta_y);

The rectangle list is a doubly linked list because the code will need to traverse the list in both directions. The list uses both a dummy head and tail to avoid special cases in link-list operations.

<rectangle list private data structure>= (U->)

typedef list_link<rectangle> rl_link;
rl_link top, bottom;
Defines rl_link (links are to index).

A new rectangle list is empty, with the dummy head and tail pointing at each other.

<the rectangle list constructor>= (U->)

rectangle_list::rectangle_list() 
  : top(rectangle(), 0, 0), 
    bottom(rectangle(), 0, 0)
  { 
   top.next = &bottom;
   bottom.last = &top;
   top.last = bottom.next = 0;
  }

To insert the rectangle r into the list, just add it to the top of the list. This maintains the list invariant: because r is the first element in the list, it must preceede any other rectangle it overlaps. Also, because none of the other rectangles have changed relative position, the invariant still holds for them.

<insert a new rectangle>= (U->)

void rectangle_list::insert(rectangle & r) {
  new rl_link(r, &top, top.next);
  }
Defines insert (links are to index).

Because the rectangle list only indicates the rectangles' relative levels instead of their absolute levels, printing the list by level requires a little bit of work. First, the rectangles must be assigned their absolute levels; then the rectangles are printed out level by level starting from the lowest level.

Because none of the print routines change the retangles' relative order in the list, they preserve the list invariant.

<print the rectangle list>= (U->) [D->]

void rectangle_list::print(ostream & out) {

  const unsigned max_level = set_levels();

  for (unsigned i = 0; i <= max_level; i++)
    print_levels(out, i);
  }
Defines print (links are to index).

Determine the levels of the rectangles in the list; return the largest level found. This is an O(n2) algorithm, where n is the number of rectangles in the list, because any two rectangles in the list may overlap and so need to be checked. However, the list invariant does help a bit: for any rectangle r, it is only necessary to check the rectangles below r in the list, because only only those rectangles below r can possibly contribute to r's level.

<print the rectangle list>+= (U->) [<-D->]

unsigned rectangle_list::set_levels(void) {

  unsigned max_level = 0;

  for (rl_link * i = bottom.last; i != &top; i = i->last) {
    i->element.level = 0;
    for (rl_link * j = i->next; j != &bottom; j = j->next)
      if (j->element.overlapping(i->element))
        i->element.level = max(i->element.level, j->element.level + 1);
    max_level = max(max_level, i->element.level);
    }

  return max_level;
  }
Defines set_levels (links are to index).

Print to the stream out all the rectangles in the list at level l.

<print the rectangle list>+= (U->) [<-D]

void rectangle_list::print_levels(ostream & out, unsigned l) const {

  for (rl_link * i = bottom.last; i != &top; i = i->last) {
    rectangle & r = i->element;
    if (r.level == l) {
      r.print(out);
      out << "\n";
      }
    }
  }
Defines print_levels (links are to index).

Slide the topmost rectangle that encloses the point (x, y) by the amount (delta_x, delta_y); if no such rectangle exists, do nothing.

<slide a rectangle>= (U->) [D->]

void rectangle_list::slide(int x, int y, int delta_x, int delta_y) {

  rl_link * rp = enclosing(x, y);
  if (rp) {
    mark_overlapping(rp);
    rl_link * rp2 = extract_marked();
    assert(rp2 == rp);
    add(rp);
    rp->element.slide(delta_x, delta_y);
    }
  }
Defines slide (links are to index).

Mark in the rectangle list the transative closure of the rectangles overlapping above the rectangle contained in rp.

<slide a rectangle>+= (U->) [<-D->]

void rectangle_list::mark_overlapping(rl_link * rp) {

  assert(rp && (rp != &top) && (rp != &bottom));

  rp->element.marked = true;
  for (rl_link * i = rp->last; i != &top; i = i->last) {
    rectangle & r = i->element;
    for (rl_link * j = i->next; j != rp->next; j = j->next)
      if ((r.marked = j->element.marked && j->element.overlapping(r)))
        break;
    }

  for (rl_link * i = rp->next; i != &bottom; i = i->next)
    i->element.marked = false;
  }
Defines mark_overlapping (links are to index).

Remove all the marked rectangles from the rectangle list. The removed rectangles are linked into a list having the same relative order as the rectangle list; return a pointer to the bottom of the removed rectangle list.

<slide a rectangle>+= (U->) [<-D->]

rectangle_list::rl_link * rectangle_list::extract_marked(void) { 

  rectangle r;
  rl_link t(r, 0, 0);
  rl_link * tp = &t;
  rl_link * i = top.next;
  const rl_link * stop = &bottom;
  while (i != stop)
    if (i->element.marked) {
      rl_link * j = i;
      i = i->next;
      j->unlink();
      tp->next = j;
      j->last = tp;
      tp = j;
      }
    else
      i = i->next;

  t.next->last = 0;
  tp->next = 0;

  return tp;
  }
Defines extract_marked (links are to index).

Return a pointer to the highest-level rectangle in the list that contains the point (x, y). If no such rectangle exists, return 0.

<slide a rectangle>+= (U->) [<-D->]

rectangle_list::rl_link * rectangle_list::enclosing(int x, int y) const {

  for (rl_link * i = top.next; i != &bottom; i = i->next)
    if (i->element.contains(x, y)) return i;

  return 0;
  }
Defines enclosing (links are to index).

Add the list of rectangle links pointed to by rp at the head of the list.

<slide a rectangle>+= (U->) [<-D]

void rectangle_list::add(rl_link * rp) {

  assert(rp);

  top.next->last = rp;
  rp->next = top.next;
  for (top.next = rp; top.next->last; top.next = top.next->last) { }
  top.next->last = &top;
  }
Defines add (links are to index).

<rect-list.cc>=

#include <algorithm>
#include "rect-list.h"

<the rectangle list constructor>

<insert a new rectangle>

<print the rectangle list>

<slide a rectangle>
Defines rect-list.cc (links are to index).

<rect-list.h>=

#ifndef _rect_list_h_defined_
#define _rect_list_h_defined_

#include "link.h"
#include "rectangle.h"
#include <iostream>

class rectangle_list {

  public:

    <rectangle list public routines>

  private:

    <rectangle list private data structure>

    unsigned set_levels(void);
    void print_levels(ostream &, unsigned) const;
    rl_link * enclosing(int, int) const;
    void mark_overlapping(rl_link *);
    rl_link * extract_marked(void);
    void add(rl_link *);

  };

#endif
Defines rect-list.h (links are to index).

Rectangles

The two public data members available in a rectangle are uninterpreted by the rectangle class itself.
<rectangle public data>= (U->)

bool marked;
unsigned level;

A rectangle is defined by the endpoints of one of its diagonals. Zero-area rectangles aren't allowed, so die when attempting to construct one.

<the rectangle constructor>= (U->)

rectangle(int X1, int Y1, int X2, int Y2) : 
  marked(false), level(0), x1(X1), y1(Y1), x2(X2), y2(Y2) { 
    assert((x1 != x2) && (y1 != y2));
    }
Defines rectangle (links are to index).

The default rectangle constructor isn't necessary, but having one makes writing the rectangle-list code a bit simpler.

<the default rectangle constructor>= (U->)

rectangle() :
  marked(false), level(0), x1(0), y1(0), x2(1), y2(1) { }

Return true if and only if rectangle r overlaps with some part of this rectangle. Two rectangles overlap if and only if they overlap both along their x range and along their y range.

<the rectangle overlapping predicate>= (U->)

bool overlapping(const rectangle & r) const {
  return intervals_overlapping(r.x1, r.x2, x1, x2) && 
         intervals_overlapping(r.y1, r.y2, y1, y2);
  }
Defines overlapping (links are to index).

Return true if and only if this rectangle contains the point (x, y).

<the rectangle contains predicate>= (U->)

bool contains(int x, int y) const {
  return coordinate_in_range(x1, x, x2) && coordinate_in_range(y1, y, y2);
  }
Defines contains (links are to index).

Slide the rectangle by (x, y).

<the rectangle slide operator>= (U->)

void slide(int x, int y) {
  x1 += x;
  y1 += y;
  x2 += x;
  y2 += y;
  }
Defines slide (links are to index).

Print this rectangle to the stream out.

<the rectangle print operator>= (U->)

void print(ostream & out) const {
  out << x1 << " " << y1 << " x " << x2 << " " << y2;
  }
Defines print (links are to index).

Return true if and only if a is in the range (l, r).

<rectangle private routines>= (U->) [D->]

bool coordinate_in_range(int l, int a, int r) const {
  return (l < a) && (a < r);
  }
Defines coordinate_in_range (links are to index).

Return true if and only if the interval [x1, x2] overlaps with the interval (x3, x4).

<rectangle private routines>+= (U->) [<-D]

bool intervals_overlapping(int x1, int x2, int x3, int x4) const {
  return (max(x1, x2) > min(x3, x4)) && (min(x1, x2) < max(x3, x4));
  }
Defines interval_overlapping (links are to index).

<rectangle.h>=

#ifndef _rectangle_h_defined_
#define _rectangle_h_defined_

#include <algorithm>
#include <cassert>

class rectangle {

  public:

    <rectangle public data>

    <the rectangle constructor>

    <the default rectangle constructor>

    <the rectangle overlapping predicate>

    <the rectangle contains predicate>

    <the rectangle slide operator>

    <the rectangle print operator>

  private:

    int x1, y1, x2, y2;

    <rectangle private routines>
  };

#endif
Defines rectangle.h (links are to index).

The Linked-List Link

This is the same linked-list data structure that's been used in previous assignments. The only change made for this assignment is the new public member function unlink(), which is written under the assumption that the linked list is using a dummy head and tail so that all valid members of the list will always have non-zero next and last pointers.
<link.h>=

#ifndef _link_h_defined_
#define _link_h_defined_

#include <cassert>

template<typename ElementType>
struct list_link {

  list_link<ElementType> * next;
  list_link<ElementType> * last;
  ElementType element;

  list_link(
    ElementType e, 
    list_link<ElementType> * before = 0,
    list_link<ElementType> * after = 0
    ) {
    element = e;

    if ((0 == before) && (0 == after)) 
      next = last = 0;
    else if (0 == before) {
      next = after;
      assert(0 == after->last);
      last = after->last;
      after->last = this;
      }
    else if (0 == after) {
      assert(0 == before->next);
      next = before->next;
      last = before;
      before->next = this;
      }
    else {
      next = after;
      last = before;
      assert((before == after->last) && (after == before->next));
      after->last = this;
      before->next = this;
      }
    }

 ~list_link() {
    if (next != 0) next->last = last;
    if (last != 0) last->next = next;
    }  

  void unlink(void) {
    assert(next && last);
    last->next = next;
    next->last = last;
    last = next = 0;
    }
  };

#endif
Defines link (links are to index).

Index


This page last modified on 6 April 2001.