6.S096 | January IAP 2014 | Undergraduate

Effective Programming in C and C++

Assignments

ASSIGNMENTS SUPPORTING FILES
Assignment 1

Problem 1: Floating Point (PDF)

Sample Solution to Assignment 1, Problem 1

Test data: floating.data (ZIP) (This ZIP file contains: 7 .in files and 7 .out files.)

Problem 2: Matrix Manipulation (PDF)

Sample Solution to Assignment 1, Problem 2

Test data: matrix.data (ZIP) (This ZIP file contains: 10 .in files and 10 .out files.)

Problem 3: Matrix Manipulation 2 (PDF)

Sample Solution to Assignment 1, Problem 3

Test data: matrix2.data (ZIP - 20.7MB) (This ZIP file contains: 11 .in files and 11 .out files.)

Problem 4: Transposition Cipher (PDF)

Sample Solution to Assignment 1, Problem 4

Test data: loop.data (ZIP - 19.1MB) (This ZIP file contains: 10 .in files and 10 .out files.)

Assignment 2

Problem 1: Linked List Library (PDF)

Sample Solution to Assignment 2, Problem 1

Solution code and test data: list (ZIP) (This ZIP file contains: 2 .h files, 2 .c files, 1 .make file and 1 .txt file.)

Problem 2: Minimum Spanning Tree (PDF)

Sample Solution to Assignment 2, Problem 2

Solution code and test data: mst (ZIP - 5.3MB) (This ZIP file contains: 1 .make file and 1 .cpp file.)

Problem 3: Rational Number Library (PDF)

Sample Solution to Assignment 2, Problem 3

Solution and test data: rational (ZIP)  (This ZIP file contains: 2 .h files, 3 .cpp files, 1 .make file and 1 .txt file.)

Assignment 3

Problem 1: C++ Linked List Library (PDF)

Sample Solution to Assignment 3, Problem 1

Solution and test data: cpplist (ZIP) (This ZIP file contains: 3 .h files, 4 .cpp files, 1 .make file and 1 .txt file.)

**Assignment 4 (Final Project)
**

Problem: N-Body Gravity Simulation (problem statement available in Lecture Notes for Lecture 8)

Starting environment: nbody (ZIP - 2.2MB) (This ZIP file contains: 5 .h files, 5 .mk files, 7 .cpp files, 1 .make file and 1 .txt file.)

More Details on Assignment 4 (Final Project)

The following should be included as your final project deliverables:

  • Zip file containing the group’s project environment that contains the full source code and can be used for setup.
  • Overview of the project: What are the important features you’ve created? What was challenging, and what have you achieved?
  • A short write-up describing the division of work. For example, “We decided to have person X and Y work primarily on the physics engine while Z developed the visualization components…”
  • A short description of the project’s structure, including descriptions of important classes and their interactions. If you can make a readable diagram of the interactions, you can submit that rather than a text description.
  • Screenshots of the results and description of how to operate your project. Examples include images of the visualization in operation, what happens when you run certain n-body systems, etc.

The operative words for the written bits are “short” and “concise”—don’t write an essay. Describe enough to give an overview of the system; if your code is well written, you should have to write very little!

Code Review

You should choose a bite-sized chunk that will take you approximately 45 minutes to an hour to fully review. The following should be included when submitting a code review:

  • The snippet of code you are reviewing: more than 30 lines, less than 100.
  • Your comments interspersed in their code.
  • A summary of main points relating to the review (what they did well, major areas for improvement, common issues, general observations).

Use the C++ Coding Standards and Best Practices provided to guide your review.

« Back to Assignments

/*

PROG: floating

LANG: C

*/

#include <stdio.h>

#include <stdlib.h>

#include <stdint.h>

#include <math.h>

#define ABSOLUTE_WIDTH 31

#define MANTISSA_WIDTH 23

#define EXPONENT_WIDTH 8

#define EXPONENT_MASK 0xffu

#define MANTISSA_MASK 0x007fffffu

#define EXPONENT_BIAS 127

union float_bits {

float f;

uint32_t bits;

};

void print_float( FILE *output, float f ) {

union float_bits t; t.f = f;

uint32_t sign_bit = ( t.bits » ABSOLUTE_WIDTH );

uint32_t exponent = ( t.bits » MANTISSA_WIDTH ) & EXPONENT_MASK;

uint32_t mantissa = ( t.bits  &  MANTISSA_MASK );

if( sign_bit != 0 ) {

fprintf( output, “-” );

}

if( exponent > 2 * EXPONENT_BIAS ) {

fprintf( output, “Inf\n” ); /* Infinity */

return;

} else if( exponent == 0 ) {

fprintf( output, “0.” ); /* Zero or Denormal */

exponent = ( mantissa != 0 ) ? exponent + 1 : exponent;

} else {

fprintf( output, “1.” ); /* Usual */

}

for( int k = MANTISSA_WIDTH - 1; k >= 0; –k ) {

fprintf( output, “%d”, ( mantissa » k ) & 1 );

}

if( exponent != 0 || mantissa != 0 ) {

fprintf( output, " * 2^%d\n", (int) ( exponent - EXPONENT_BIAS ) );

}

}

int main() {

FILE *input  = fopen( “floating.in”,  “r” ),

*output = fopen( “floating.out”, “w” );

size_t N; float f;

fscanf( input, “%zu”, &N );

for( size_t i = 0; i < N; ++i ) {

fscanf( input, “%f”, &f );

print_float( output, f );

}

fclose( input );

fclose( output );

return 0;

}

Below is the output using the test data:

floating:

1: OK [0.004 seconds] OK!

2: OK [0.004 seconds] OK!

3: OK [0.004 seconds] OK!

4: OK [0.004 seconds] OK!

5: OK [0.005 seconds] OK!

6: OK [0.004 seconds] OK!

7: OK [0.004 seconds] OK!

« Back to Assignments

« Back to Assignments

/*

PROG: floating

LANG: C

*/

#include <stdio.h>

#include <stdlib.h>

#include <stdint.h>

#include <math.h>

#define ABSOLUTE_WIDTH 31

#define MANTISSA_WIDTH 23

#define EXPONENT_WIDTH 8

#define EXPONENT_MASK 0xffu

#define MANTISSA_MASK 0x007fffffu

#define EXPONENT_BIAS 127

union float_bits {

float f;

uint32_t bits;

};

void print_float( FILE *output, float f ) {

union float_bits t; t.f = f;

uint32_t sign_bit = ( t.bits » ABSOLUTE_WIDTH );

uint32_t exponent = ( t.bits » MANTISSA_WIDTH ) & EXPONENT_MASK;

uint32_t mantissa = ( t.bits  &  MANTISSA_MASK );

if( sign_bit != 0 ) {

fprintf( output, “-” );

}

if( exponent > 2 * EXPONENT_BIAS ) {

fprintf( output, “Inf\n” ); /* Infinity */

return;

} else if( exponent == 0 ) {

fprintf( output, “0.” ); /* Zero or Denormal */

exponent = ( mantissa != 0 ) ? exponent + 1 : exponent;

} else {

fprintf( output, “1.” ); /* Usual */

}

for( int k = MANTISSA_WIDTH - 1; k >= 0; –k ) {

fprintf( output, “%d”, ( mantissa » k ) & 1 );

}

if( exponent != 0 || mantissa != 0 ) {

fprintf( output, " * 2^%d\n", (int) ( exponent - EXPONENT_BIAS ) );

}

}

int main() {

FILE *input  = fopen( “floating.in”,  “r” ),

*output = fopen( “floating.out”, “w” );

size_t N; float f;

fscanf( input, “%zu”, &N );

for( size_t i = 0; i < N; ++i ) {

fscanf( input, “%f”, &f );

print_float( output, f );

}

fclose( input );

fclose( output );

return 0;

}

Below is the output using the test data:

floating:

1: OK [0.004 seconds] OK!

2: OK [0.004 seconds] OK!

3: OK [0.004 seconds] OK!

4: OK [0.004 seconds] OK!

5: OK [0.005 seconds] OK!

6: OK [0.004 seconds] OK!

7: OK [0.004 seconds] OK!

« Back to Assignments

« Back to Assignments

/*

PROG: matrix

LANG: C

*/

#include <stdio.h>

#include <stdlib.h>

#define MAXN 300

typedef struct Matrix {

size_t R, C;

int index[MAXN][MAXN];

} Matrix;

void read_matrix( FILE *fin, Matrix *matrix ) {

fscanf( fin, “%zu %zu”, &matrix->R, &matrix->C );

if( matrix->R >= MAXN || matrix->C >= MAXN ) {

printf( “Error: tried to read matrix with a dimension larger than %d\n”, MAXN );

exit( EXIT_FAILURE );

}

for( size_t r = 0; r < matrix->R; ++r ) {

for( size_t c = 0; c < matrix->C; ++c ) {

fscanf( fin, “%d”, &matrix->index[r][c] );

}

}

}

void print_matrix( FILE *fout, Matrix *matrix ) {

fprintf( fout, “%zu %zu\n”, matrix->R, matrix->C );

for( size_t r = 0; r < matrix->R; ++r ) {

for( size_t c = 0; c < matrix->C - 1; ++c ) {

fprintf( fout, “%d “, matrix->index[r][c] );

}

fprintf( fout, “%d\n”, matrix->index[r][matrix->C - 1] );

}

}

void mult_matrix( Matrix *a, Matrix *b, Matrix *prod ) {

if( a->C != b->R ) {

printf( “Error: tried to multiply (%zux%zu)x(%zux%zu)\n”, a->R, a->C, b->R, b->C );

exit( EXIT_FAILURE );

}

size_t inner = a->C;

prod->R = a->R;

prod->C = b->C;

for( size_t r = 0; r < prod->R; ++r ) {

for( size_t c = 0; c < prod->C; ++c ) {

prod->index[r][c] = 0;

for( size_t i = 0; i < inner; ++i ) {

prod->index[r][c] += a->index[r][i] * b->index[i][c];

}

}

}

}

int main(void) {

FILE *fin = fopen( “matrix.in”, “r” ),

*fout = fopen( “matrix.out”, “w” );

if( fin == NULL ) {

printf( “Error: could not open matrix.in\n” );

exit( EXIT_FAILURE );

}

if( fin == NULL ) {

printf( “Error: could not open matrix.out\n” );

exit( EXIT_FAILURE );

}

Matrix a, b, c;

read_matrix( fin, &a );

read_matrix( fin, &b );

fclose( fin );

mult_matrix( &a, &b, &c );

print_matrix( fout, &c );

fclose( fout );

return 0;

}

Below is the output using the test data:

matrix:

1: OK [0.004 seconds]

2: OK [0.004 seconds]

3: OK [0.004 seconds]

4: OK [0.013 seconds]

5: OK [0.009 seconds]

6: OK [0.006 seconds]

7: OK [0.011 seconds]

8: OK [0.011 seconds]

9: OK [0.012 seconds]

10: OK [0.004 seconds]

« Back to Assignments

« Back to Assignments

/*

PROG: matrix2

LANG: C

*/

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

typedef struct Matrix_s {

size_t R, C;

int *index;

} Matrix;

Matrix* allocate_matrix( size_t R, size_t C ) {

Matrix *matrix = malloc( sizeof( Matrix ) );

matrix->R = R;

matrix->C = C;

matrix->index = malloc( R * C * sizeof( int ) );

return matrix;

}

void destroy_matrix( Matrix *matrix ) {

free( matrix->index );

free( matrix );

}

typedef enum {

REGULAR = 0,

TRANSPOSE = 1

} Transpose;

// Allowing reading a matrix in as either regular or transposed

Matrix* read_matrix( FILE *input, Transpose orient ) {

size_t R, C;

fscanf( input, “%zu %zu”, &R, &C );

Matrix *matrix = NULL;

if( orient == REGULAR ) {

matrix = allocate_matrix( R, C );

for( size_t r = 0; r < matrix->R; ++r ) {

for( size_t c = 0; c < matrix->C; ++c ) {

fscanf( input, “%d”, &matrix->index[c + r * C] );

}

}

} else if( orient == TRANSPOSE ) {

matrix = allocate_matrix( C, R );

for( size_t r = 0; r < matrix->C; ++r ) {

for( size_t c = 0; c < matrix->R; ++c ) {

fscanf( input, “%d”, &matrix->index[r + c * R] );

}

}

} else {

fprintf( stderr, “Error: unknown orientation %d.\n”, orient );

exit( EXIT_FAILURE );

}

return matrix;

}

void print_matrix( FILE *output, Matrix *matrix ) {

fprintf( output, “%zu %zu\n”, matrix->R, matrix->C );

for( size_t r = 0; r < matrix->R; ++r ) {

for( size_t c = 0; c < matrix->C - 1; ++c ) {

fprintf( output, “%d “, matrix->index[c + r * matrix->C] );

}

fprintf( output, “%d\n”, matrix->index[matrix->C - 1 + r * matrix->C] );

}

}

Matrix* product_matrix( Matrix *a, Matrix *b ) {

if( a->C != b->C ) {

printf( “Error: tried to multiply (%zux%zu)x(%zux%zu)\n”, a->R, a->C, b->C, b->R );

exit( EXIT_FAILURE );

}

Matrix *prod = allocate_matrix( a->R, b->R );

size_t nRows = prod->R, nCols = prod->C, nInner = a->C;

for( size_t r = 0; r < nRows; ++r ) {

for( size_t c = 0; c < nCols; ++c ) {

prod->index[c + r * nCols] = 0;

for( size_t i = 0; i < nInner; ++i ) {

prod->index[c + r * nCols] += a->index[i + r * nInner] * b->index[i + c * nInner];

}

}

}

return prod;

}

int main(void) {

FILE *fin = fopen( “matrix2.in”, “r” );

if( fin == NULL ) {

printf( “Error: could not open matrix2.in\n” );

exit( EXIT_FAILURE );

}

Matrix *a = read_matrix( fin, REGULAR );

Matrix *b = read_matrix( fin, TRANSPOSE );

fclose( fin );

Matrix *c = product_matrix( a, b );

FILE *output = fopen( “matrix2.out”, “w” );

if( output == NULL ) {

printf( “Error: could not open matrix2.out\n” );

exit( EXIT_FAILURE );

}

print_matrix( output, c );

fclose( output );

destroy_matrix( a );

destroy_matrix( b );

destroy_matrix( c );

return 0;

}

Below is the output using the test data:

matrix2:

1: OK [0.006 seconds]

2: OK [0.007 seconds]

3: OK [0.007 seconds]

4: OK [0.019 seconds]

5: OK [0.017 seconds]

6: OK [0.109 seconds]

7: OK [0.178 seconds]

8: OK [0.480 seconds]

9: OK [0.791 seconds]

10: OK [1.236 seconds]

11: OK [2.088 seconds]

« Back to Assignments

« Back to Assignments


#include <"list.h">
 
#include <stdio.h>
#include <stdlib.h>
 
struct List_node_s {
  List_node *next;
  int value;
};
 
List empty_list( void ) {
  return (List) { .length = 0, .front = NULL };
}
 
List_node* create_node( int value ) {
  List_node *new_node = malloc( sizeof( List_node ) );
  new_node->value = value;
  new_node->next = NULL;
  return new_node;
}
 
void list_append( List *list, int value ) {
  if( list->front == NULL ) {
    list->front = create_node( value );
  } else {
    List_node *p = list->front;
    for( size_t i = 1; i < list->length; ++i, p = p->next );
    p->next = create_node( value );
  }
  ++list->length;
}
 
void list_delete_from_front( List *list, int  value ) {
  List_node *front = list->front;
  while( front != NULL && front->value == value ) {
    list->front = front->next;
    --list->length;
    free( front );
    front = list->front;
  }
}
 
void list_delete( List *list, int value ) {
  list_delete_from_front( list, value );
  if( list->front == NULL ) {
    return;
  }
 
  List_node *prev = list->front;
  List_node *p = list->front->next;
 
  while( p != NULL ) {
    if( p->value == value ) {
      prev->next = p->next;
      free( p ); --list->length;
      p = prev->next;
    } else {
      prev = p;
      p = prev->next;
    }
  }
}
 
void list_insert_before( List *list, int insert, int before ) {
  if( list->front != NULL && list->front->value == before ) {
    List_node *new_node = create_node( insert );
    new_node->next = list->front;
    list->front = new_node;
    ++list->length;
  } else {
    List_node *prev = list->front;
    List_node *next = list->front->next;
    while( next != NULL ) {
      if( next->value == before ) {
        prev->next = create_node( insert );
        prev->next->next = next;
        ++list->length; return;
      }
      prev = next;
      next = next->next;
    }
  }
}
 
 
void list_apply( List *list, int (*function_ptr)( int) ) {
  for( List_node *p = list->front; p != NULL; p = p->next ) {
    p->value = (*function_ptr)( p->value );
  }
}
 
int list_reduce( List *list, int (*function_ptr)(int, int) ) {
  if( list->front == NULL ) {
    return 0;
  }
 
  int result = list->front->value;
 
  for( List_node *p = list->front->next; p != NULL; p = p->next ) {
    result = (*function_ptr) ( result, p->value );
  }
 
  return result;
}
 
void list_print( List list ) {
  if( list.front == NULL ) {
    printf( "{}\n" );
  } else {
    printf( "{ " );
 
    List_node *p = list.front;
    size_t length = list.length;
 
    while( p->next != NULL && length > 0 ) {
      printf( "%d -> ", p->value );
      p = p->next; --length;
    }
    printf( "%d }\n", p->value );
 
    if( length != 1 ) {
      printf( "Error: badly formed list.\n" );
      exit( EXIT_FAILURE );
    }
  }
}
 
 
void list_clear( List *list ) {
  List_node *front = list->front;
  size_t length = list->length;
 
  while( front != NULL && length > 0 ) {
    List_node *next = front->next;
    free( front );
    front = next;
    --length;
  }
 
  if( length != 0 ) {
    printf( "Error: failed to clean up list properly.\n" );
    exit( EXIT_FAILURE );
  }
}

Below is the output using the test data:


list:
 1: OK [0.002 seconds] OK!
 2: OK [0.004 seconds] OK!
 3: OK [0.035 seconds] OK!
 4: OK [2.175 seconds] OK!
 5: OK [0.133 seconds] OK!
 6: OK [0.305 seconds] OK!
 7: OK [0.061 seconds] OK!
 8: OK [0.213 seconds] OK!
 9: OK [0.002 seconds] OK!
10: OK [1.054 seconds] OK!

« Back to Assignments

« Back to Assignments


/*
PROG: mst
LANG: C++
*/
#include <vector>
#include <queue>
#include <fstream>
#include <iostream>
#include <iomanip>
#include <unordered_map>
 
class State {
  size_t _node;
  double _dist;
public:
  State( size_t aNode, double aDist ) : _node{aNode}, _dist{aDist} {}
  inline size_t node()const { return _node; }
  inline double dist()const { return _dist; }
};
 
class AdjacencyList {
  std::vector< std::vector< State>  > _adj;
  AdjacencyList() = delete;
public:
  AdjacencyList( std::istream &input );
  inline size_t size() const { return _adj.size(); }
  inline const std::vector& adj(size_t node )  const { return _adj[node]; }
  void print();
};
 
inline bool operator<( const State &a, const State &b ) {
  return a.dist() > b.dist();
}
 
AdjacencyList::AdjacencyList( std::istream &input ) : _adj{} {
  size_t nNodes; size_t nEdges; input >> nNodes >> nEdges;
  _adj.resize( nNodes );
 
  for( size_t e = 0; e < nEdges; ++e ) {
    size_t v, w; double weight;
    input >> v >> w >> weight;
    // Add this edge to both the v and w lists
    _adj[v].push_back( State{ w, weight } );
    _adj[w].push_back( State{ v, weight } );
  }
}
 
void AdjacencyList::print() {
  for( size_t i = 0; i < _adj.size(); ++i ) {
    std::cout << i << ": ";
    for( auto state : _adj[i] ) {
      std::cout << "(" << state.node() << ", " << state.dist() << ") ";
    }
    std::cout << "\n";
  }
}
 
double prim( const AdjacencyList &adj ) {
  std::unordered_map<int, bool> visited;
  std::priority_queue<State> pq;
 
  pq.push( State{ 0, 0.0 } );
  double weight = 0.0;
 
  while( visited.size() < adj.size() ) {
    auto top = pq.top(); pq.pop();
 
    if( visited.count( top.node() ) == 0 ) {
      visited[top.node()] = true;
      weight += top.dist();
 
      for( auto vertex : adj.adj( top.node() ) ) {
        pq.push( vertex );
      }
    }
  }
 
  return weight;
}
 
int main() {
  std::ifstream input{ "mst.in" };
  std::ofstream output{ "mst.out" };
 
  if( input.is_open() ) {
    auto adj = AdjacencyList{ input };
    output << std::fixed << std::setprecision( 8 );
    output << prim( adj ) << "\n";
  } else {
    std::cerr << "Could not open mst.in\n";
    return 1;
  }
   
  return 0;
}

Below is the output using the test data:


mst:
 1: OK [0.004 seconds]
 2: OK [0.004 seconds]
 3: OK [0.004 seconds]
 4: OK [0.006 seconds]
 5: OK [0.093 seconds]
 6: OK [0.122 seconds]
 7: OK [0.227 seconds]
 8: OK [0.229 seconds]
 9: OK [0.285 seconds]
10: OK [0.287 seconds]

« Back to Assignments

« Back to Assignments

Here are the contents of rational.h:


#ifndef _6S096_RATIONAL_H
#define _6S096_RATIONAL_H
 
#include <cstdint>
#include <iosfwd>
#include <stdexcept>
 
class Rational {
  intmax_t _num, _den;
public:
  enum sign_type { POSITIVE, NEGATIVE };
 
  Rational() : _num{0}, _den{1} {}
  Rational( intmax_t numer ) : _num{numer}, _den{1} {}
  Rational( intmax_t numer, intmax_t denom ) : _num{numer}, _den{denom} { normalize(); }
 
  inline intmax_t num() const { return _num; }
  inline intmax_t den() const { return _den; }
 
  void normalize();
  float to_float()const;
  double to_double()const;
  sign_type sign() const;
  Rational inverse() const;
};
 
std::ostream& operator<<( std::ostream& os, const Rational &ratio );
 
inline bool operator==( const Rational &lhs, const Rational &rhs ) {
  return lhs.num() * rhs.den() == rhs.num() * lhs.den();
}
 
inline bool operator<( const Rational &lhs, const Rational &rhs ) {
  if( lhs.sign() == Rational::POSITIVE && rhs.sign() == Rational::POSITIVE ) {
      return lhs.num() * rhs.den() < rhs.num() * lhs.den();
  } else if( lhs.sign() == Rational::NEGATIVE && rhs.sign() == Rational::NEGATIVE ) {
    return lhs.num() * rhs.den() > rhs.num() * lhs.den();
  } else {
    return lhs.sign() == Rational::NEGATIVE;
  }
}
 
inline Rational operator*( const Rational &a, const Rational &b ) {
  return Rational{ a.num() * b.num(), a.den() * b.den() };
}
 
inline Rational operator+( const Rational &a, const Rational &b ) {
  return Rational{ a.num() * b.den() + b.num() * a.den(), a.den() * b.den() };
}
 
inline Rational operator-( const Rational &a, const Rational &b ) {
  return Rational{ a.num() * b.den() - b.num() * a.den(), a.den() * b.den() };
}
 
inline Rational operator/( const Rational &a, const Rational &b ) {
  return a * b.inverse();
}
 
class bad_rational : public std::domain_error {
public:
  explicit bad_rational() : std::domain_error("Bad rational: zero denominator" ) {}
};
 
#endif // _6S096_RATIONAL_H

Here is the source code file rational.cpp:


#include "rational.h"
#include "gcd.h"
 
#include <stdexcept>
#include <ostream>
#include <iostream>
#include <cmath>
 
Rational Rational::inverse() const {
  return Rational{ _den, _num };
}
 
Rational::sign_type Rational::sign()const {
  return _num >= 0 ? POSITIVE : NEGATIVE;
}
 
std::ostream& operator<<( std::ostream& os, const Rational &ratio ) {
  if( ratio == 0 ) {
    os << "0";
  } else {
    if( ratio.sign() == Rational::NEGATIVE ) {
      os << "-";
    }
    os << std::abs( ratio.num() ) << "/" << std::abs( ratio.den() );
  }
  return os;
}
 
void Rational::normalize() {
  if( _den == 0 ) {
    throw bad_rational();
  }
 
  if( _num == 0 ) {
    _den = 1; return;
  }
 
  auto g = gcd( std::abs( _num ), std::abs( _den ) );
  _num /= g; _den /= g;
 
  if( _den < 0 ) {
    _num = -_num;
    _den = -_den;
  }
}
 
float Rational::to_float() const {
  return static_cast<float>( _num ) / static_cast<float>( _den );
}
 
double Rational::to_double()const {
  return static_cast<double>( _num ) / static_cast<double>( _den );
}

Below is the output using the test data:


rational:
 1: OK [0.007 seconds] OK! add
 2: OK [0.006 seconds] OK! mult
 3: OK [0.009 seconds] OK! add1024
 4: OK [0.014 seconds] OK! add1024
 5: OK [0.158 seconds] OK! add32768
 6: OK [0.007 seconds] OK! op<<
 7: OK [0.289 seconds] OK! div65536 in 0.280000 s
 8: OK [0.006 seconds] OK! phi, 0.000000e+00
 9: OK [0.006 seconds] OK! (Bad rational: zero denominator)
10: OK [0.006 seconds] OK! xyz
11: OK [0.007 seconds] OK! pow2
12: OK [0.006 seconds] OK! x1z

« Back to Assignments

« Back to Assignments

Look in list.h for a sense of the structure of the solution. The big idea to speed up the reduce/apply functions while also giving users a nice way to iterate over the items in the list is to create an “iterator” type within our class. Users will be able to write code similar to the STL:


// Print out every item in the list
for( List::iterator it = list.begin(); it != list.end(); ++it ) {
    std::cout < < *it << "\n";
}

To speed up our “append” function, the List class will also store a pointer to the very last element in the current list.

Directory structure:

  • GRADER_INFO.txt
  • include
    • apply.h
    • list.h
    • list_node.h
    • reduce.h
  • Makefile
  • src
    • apply.cpp
    • list.cpp
    • list_iterator.cpp
    • list_node.cpp
    • reduce.cpp
    • test.cpp

Here are the contents of apply.h:


#ifndef _6S096_CPPLIST_APPLY_H
#define _6S096_CPPLIST_APPLY_H
#include "list.h"
 
class ApplyFunction {
protected:
  virtual int function( int x ) const = 0;
public:
  void apply( List &list ) const;
  virtual ~ApplyFunction() {}
};
 
// An example ApplyFunction (see apply.cpp)
class SquareApply : public ApplyFunction {
  int function( int x ) const;
};
 
#endif // _6S096_CPPLIST_APPLY_H

Here are the contents of list.h:


#ifndef _6S096_CPPLIST_H
#define _6S096_CPPLIST_H
#include <cstddef>
#include <stdexcept>
 
class ApplyFunction;
class ReduceFunction;
class ListNode;
 
class List {
  size_t _length;
  ListNode *_begin;
  ListNode *_back;
 
public: 
  // Can use outside as List::iterator type
  class iterator {
    // Making List a friend class means we'll be able to access
    // the private _node pointer data within the scope of List.
    friend class List;
    ListNode *_node;
  public:
    iterator( ListNode *theNode );
    iterator& operator++();
    int& operator*();
    bool operator==( const iterator &rhs );
    bool operator!=( const iterator &rhs );
  };
  // Can use outside as List::const_iterator type
  class const_iterator {
    // Again, this is basically the only situation you should
    // be using the keyword 'friend'
    friend class List;
    ListNode *_node;
  public:
    const_iterator( ListNode *theNode );
    const_iterator& operator++();
    const int& operator*();
    bool operator==( const const_iterator &rhs );
    bool operator!=( const const_iterator &rhs );
  };
 
  List();
  List( const List &list );
  List& operator=( const List &list );
  ~List();
  size_t length()const;
  int& value( size_t pos );
  int value( size_t pos ) const;
  bool empty() const;
 
  iterator begin();
  const_iterator begin() const;
  iterator back();
  const_iterator back() const;
  iterator end();
  const_iterator end() const;
 
  iterator find( iterator s, iterator t, int needle );
  void append( int theValue );
  void deleteAll( int theValue );
  void insertBefore( int theValue, int before );
  void insert( iterator pos, int theValue );
 
  void apply( const ApplyFunction &interface );
  int reduce( const ReduceFunction &interface ) const;
  void print() const;
  void clear();
 
private:
  ListNode* node( iterator it ) { return it._node; }
  ListNode* node( const_iterator it ) { return it._node; }
};
 
class ListOutOfBounds : public std::range_error {
public:
  explicit ListOutOfBounds() : std::range_error( "List index out of bounds" ) {}
};
 
#endif // _6S096_CPPLIST_H

Here are the contents of list_node.h:


#ifndef _6S096_CPPLIST_NODE_H
#define _6S096_CPPLIST_NODE_H
 
class ListNode {
  int _value;
  ListNode *_next;
  ListNode( const ListNode & ) = delete;
  ListNode& operator=( const ListNode & ) = delete;
public:
  ListNode();
  ListNode( int theValue );
  ~ListNode();
  int& value();
  int value() const;
  ListNode* next();
  void insertAfter( ListNode *before );
  void setNext( ListNode *nextNode );
  static void deleteNext( ListNode *before );
  static void deleteSection( ListNode *before, ListNode *after );
 
  static ListNode* create( int theValue = 0 );
};
 
#endif // _6S096_CPPLIST_NODE_H

Here are the contents of reduce.h:


#ifndef _6S096_CPPLIST_REDUCE_H
#define _6S096_CPPLIST_REDUCE_H
#include "list.h"
 
class ReduceFunction {
protected:
  virtual int function( int x, int y ) const = 0;
public:
  int reduce( const List &list ) const;
  virtual int identity() const = 0;
  virtual ~ReduceFunction() {}
};
 
// An example ReduceFunction
class SumReduce : public ReduceFunction {
  int function( int x, int y ) const;
public:
  SumReduce() {}
  ~SumReduce() {}
  int identity() const { return 0; }
};
 
// Another ReduceFunction
class ProductReduce : public ReduceFunction {
  int function( int x, int y ) const;
public:
  ProductReduce() {}
  ~ProductReduce() {}
  int identity() const { return 1; }
};
 
#endif // _6S096_CPPLIST_REDUCE_H

Here is the source code file apply.cpp:


#include "list.h"
#include "apply.h"
 
void ApplyFunction::apply( List &list ) const {
  for( auto it = list.begin(); it != list.end(); ++it ) {
    *it = function( *it );
  }
}
 
int SquareApply::function( int x ) const {
  return x * x;
}

Here is the source code file list.cpp:


#include "list.h"
#include "list_node.h"
#include "apply.h"
#include "reduce.h"
 
#include <iostream>
 
List::List() : _length{0}, _begin{ nullptr }, _back{ nullptr } {}
 
List::List( const List &list ) : _length{0}, _begin{nullptr}, _back{nullptr} {
  for( auto it = list.begin(); it != list.end(); ++it ) {
    append( *it );
  } 
}
 
List& List::operator=( const List &list ) {
  if( this != &list ) {
    clear();
    for( auto it = list.begin(); it != list.end(); ++it ) {
      append( *it );
    } 
  }
  return *this;
}
 
List::~List() { clear(); }
 
size_t List::length() const { return _length; }
 
int& List::value( size_t pos ) {
  auto it = begin();
  for( size_t i = 0; i < pos && it != end(); ++it, ++i );
  if( it == end() ) {
    throw ListOutOfBounds();
  }
 
  return *it;
}
 
int List::value( size_t pos ) const {
  auto it = begin();
  for( size_t i = 0; i < pos && it != end(); ++it, ++i );
  if( it == end() ) {
    throw ListOutOfBounds();
  }
 
  return *it;
}
 
bool List::empty() const {
 return _length == 0;
}
 
List::iterator List::begin() { return iterator{ _begin }; }
List::const_iterator List::begin() const { return const_iterator{ _begin }; }
List::iterator List::back() { return iterator{ _back }; }
List::const_iterator List::back() const { return const_iterator{ _back }; }
List::iterator List::end() { return iterator{ nullptr }; }
List::const_iterator List::end() const { return const_iterator{ nullptr }; }
 
void List::append( int theValue ) {
  auto *newNode = ListNode::create( theValue );
 
  if( empty() ) {
    newNode->setNext( _back );
    _begin = newNode;
  } else {
    newNode->insertAfter( _back );
  }
 
  _back = newNode;
  ++_length;
}
 
void List::deleteAll( int theValue ) {
  if( !empty() ) {
    // Delete from the front
    while( _begin->value() == theValue && _begin != _back ) {
      auto *newBegin = _begin->next();
      delete _begin;
      _begin = newBegin;
      --_length;
    }
 
    auto *p = _begin;
 
    if( _begin != _back ) {
      // Normal deletion from interior of list
      for( ; p->next() != _back; ) {
        if( p->next()->value() == theValue ) {
          ListNode::deleteNext( p );
          --_length;
        } else {
          p = p->next();
        }
      }
 
      // Deleting the last item
      if( _back->value() == theValue ) {
        ListNode::deleteNext( p );
        _back = p;
        --_length;
      }
    } else if( _begin->value() == theValue ) {
      // Deal with the case where we deleted the whole list
      _begin = _back = nullptr;
      _length = 0;
    }
  }
}
 
List::iterator List::find( iterator s, iterator t, int needle ) {
  for( auto it = s; it != t; ++it ) {
    if( *it == needle ) {
      return it;
    }
  }
  return t;
}
 
void List::insert( iterator pos, int theValue ) {
  auto *posPtr = node( pos );
  auto *newNode = ListNode::create( theValue );
  newNode->insertAfter( posPtr );
  ++_length;
}
 
void List::insertBefore( int theValue, int before ) {
  if( !empty() ) {
    if( _begin->value() == before ) {
      auto *newNode = ListNode::create( theValue );
      newNode->setNext( _begin );
      _begin = newNode;
      ++_length;
    } else {
      auto *p = _begin;
      for( ; p != _back && p->next()->value() != before; p = p->next() );
      if( p != _back && p->next()->value() == before ) {
        auto *newNode = ListNode::create( theValue );
        newNode->insertAfter( p );
        ++_length;
      }
    }
  }
}
 
void List::apply( const ApplyFunction &interface ) {
  interface.apply( *this );
}
 
int List::reduce( const ReduceFunction &interface ) const {
  return interface.reduce( *this );
}
 
void List::print() const {
  std::cout << "{ ";
  for( auto it = begin(); it != back(); ++it ) {
    std::cout << *it << " -> ";
  }
  if( !empty() ) {
    std::cout << *back() << " ";
  }
  std::cout << "}\n";
}
 
void List::clear() {
  for( auto *p = _begin; p != nullptr; ) {
    auto *p_next = p->next();
    delete p;
    p = p_next;
  }
  _length = 0;
  _begin = nullptr;
  _back = nullptr;
}

Here is the source code file list_iterator.cpp:


#include "list.h"
#include "list_node.h"
 
List::iterator::iterator( ListNode *theNode ) : _node{theNode} {}
List::iterator& List::iterator::operator++() { 
  _node = _node->next(); 
  return *this; 
}
int& List::iterator::operator*() { return _node->value(); }
bool List::iterator::operator==( const iterator &rhs ) { return _node == rhs._node; }
bool List::iterator::operator!=( const iterator &rhs ) { return _node != rhs._node; }
 
List::const_iterator::const_iterator( ListNode *theNode ) : _node{theNode} {}
List::const_iterator& List::const_iterator::operator++() { 
  _node = _node->next(); 
  return *this; 
}
const int& List::const_iterator::operator*() { return _node->value(); }
bool List::const_iterator::operator==( const const_iterator &rhs ) { return _node == rhs._node; }
bool List::const_iterator::operator!=( const const_iterator &rhs ) { return _node != rhs._node; }

Here is the source code file list_node.cpp:


#include "list_node.h"
 
ListNode::ListNode() : _value{0}, _next{nullptr} {}
ListNode::ListNode( int theValue ) : _value{theValue}, _next{nullptr} {}
ListNode::~ListNode() {}
int& ListNode::value() { return _value; }
int ListNode::value(){const { return _value; }
ListNode* ListNode::next() { return _next; }
 
void ListNode::insertAfter( ListNode *before ) {
  _next = before->next();
  before->_next = this;
}
 
void ListNode::setNext( ListNode *nextNode ) {
  _next = nextNode;
}
 
void ListNode::deleteNext( ListNode *before ) {
  auto *after = before->next()->next();
  delete before->next();
  before->_next = after;
}
 
void ListNode::deleteSection( ListNode *before, ListNode *after ) {
  auto *deleteFront = before->next();
  while( deleteFront != after ) {
    auto *nextDelete = deleteFront->next();
    delete deleteFront;
    deleteFront = nextDelete;
  }
}
 
ListNode* ListNode::create( int theValue ) {
  return new ListNode{ theValue };
}

Here is the source code file reduce.cpp:


#include "list.h"
#include "reduce.h"
 
int ReduceFunction::reduce(const List &list ) const {
  int result = identity();
  for( auto it = list.begin(); it != list.end(); ++it ) {
    result = function( result, *it );
  }
  return result;
}
 
int SumReduce::function( int x, int y ) const { 
  return x + y; 
}
 
int ProductReduce::function(int x, int y ) const { 
  return x * y; 
}

Below is the output using the test data:


cpplist: 
 1: OK [0.004 seconds] OK!
 2: OK [0.005 seconds] OK!
 3: OK [0.005 seconds] OK!
 4: OK [0.009 seconds] OK!
 5: OK [0.006 seconds] OK!
 6: OK [0.308 seconds] OK!
 7: OK [0.053 seconds] OK!
 8: OK [0.007 seconds] OK!
 9: OK [0.005 seconds] OK!
10: OK [0.742 seconds] OK!

« Back to Assignments

Course Info

Instructor
As Taught In
January IAP 2014
Learning Resource Types
Problem Sets with Solutions
Lecture Notes
Programming Assignments with Examples