EEN218 Autumn 2001 Test 23rd October Solutions
1. [The original question]
You are to write a program
that processes data captured from automatic sensors. The sensors are buried in
the surface of a road, and detect and measure passing vehicles. For each vehicle
that passes, the sensors take note of the time of day, the vehicle's weight,
and the vehicle's speed. These three pieces of information are stored in a file
which your program will read.
The information for each
vehicle is stored on a single line of the file, with the time first (consisting
of 3 or 4 digits followed by "am" or "pm", examples: 900am
1131pm, 1200am), then the weight (measured in tons, as a floating point number
with 2 digits before the point and three after, examples: 01.750, 00.331, 13.016),
and finally the speed (measured in miles per hour as a three digit integer,
examples: 030, 072, 005). Each of the three pieces of data are separated by
spaces.
Here is a sample from part of the data file:
945am
01.502 035
946am
01.945 029
946am
00.076 043
948am
07.732 028
Note that the file contains
data collected over a period of many weeks, and may contain many millions of
records, so do not attempt to keep all the data in memory.
Your program should read all
the data from the file, then print a simple report summarising the important
things exactly as described below.
Anything weighing less that
300 pounds should be completely ignored (it is probably a pedestrian or a
pedal-cyclist) and not represented in the summary at all. (There are 2240 pounds
in a ton).
The summary should report
three pieces of information: The average speed of all heavy vehicles (heavy
means 2 tons or more), the average speed of all light vehicles (light means
less than 2 tons), and the average speed of all vehicles travelling at night
(night is from 8:00pm to 5:59am). The output from your program should be
printed on three lines exactly as illustrated below; the averages should
be printed with one digit after the decimal point, and no unnecessary leading
zeros. Make sure your program's output is lined up correctly like the sample.
Sample output (not corresponding to sample of input)
Average
speed of: light vehicles: 101.5 mph
heavy vehicles: 28.8 mph
night travel: 39.3 mph
The file containing the data
records is called "traffic.dat"; If any information you consider
important is not given in this explanation, make a reasonable assumption, state
that assumption, and use it without worrying further.
[End of question]
We need a program that
opens the file, and (in a loop) reads the records one-by-one until there are
none left. As it reads the records, it should keep track of the total number of
vehicles (excluding those that are supposed to be ignored) and the total of the
weights in the three specified categories. As the averages are to be printed as
floats, we can reasonably keep the totals as floats too; that way there is no
likelihood of overflows.
For
reading the data, both scanf and the C++ methods are perfectly adequate. Just don't
over-specify. You are told that times have 3 or 4 digits, and weights have 2
digits before the point and 3 after, but just because you know something
doesn't mean you have to use it. Scanf's %d format will happily read an integer
whatever size it is; the %f format will correctly read a float no matter how
many digits it has before or after the point.
Remember
that scanf returns as its result the number of items successfully read (or –1
if it fails to read because of end of file). This provides a very simple way of
telling when the input is over.
There
are many correct ways to deal with the time. I think converting them all to
24-hour format is probably the easiest: 1200am to 1259am is converted to 0000
to 0059, 1200pm to 1259pm is left alone, but all other pm times have 1200 added
(so 115 becomes 1315).
Be
careful to avoid division by zero when calculating the averages at the end.
When you finally print the averages, a little effort is required to get them
lined up as required. Spaces in front of the words "heavy" and
"light" does half the job, but as we don't know how many digits each
of the averages will take, a special %f format is needed: %5.1f means a total
of 5 characters (including everything, points and all), with 1 digit after the
point.
#include
<stdio.h>
#include
<stdlib.h>
void
main(void)
{
FILE *f=fopen("traffic.dat", "r");
if (f==NULL)
{ printf("Can't open input
file\n");
exit(1); }
const float three_hundred_pounds =
300.0/2240.0;
int num_light=0, num_heavy=0, num_night=0;
float tot_light=0.0, tot_heavy=0.0,
tot_night=0.0;
while (1)
{ int time;
char ampm1, ampm2;
float weight, speed;
int n=fscanf(f, "%d%c%c %f %f",
&time, &m1,
&m2, &weight, &speed);
if (n!=5) break;
if (weight < three_hundred_pounds)
continue;
if (weight < 2.0)
{ num_light+=1;
tot_light+=speed; }
else
{ num_heavy+=1;
tot_heavy+=speed; }
if (ampm1=='a')
{ if (time >= 1200) time-=1200; }
if (ampm1=='p')
{ if (time < 1200) time+=1200; }
if (time>=2000 || time<=559)
{ num_night+=1;
tot_night+=speed; } }
fclose(f);
float avg_light=0.0, avg_heavy=0.0,
avg_night=0.0;
if (num_light!=0)
avg_light=tot_light/num_light;
if (num_heavy!=0)
avg_heavy=tot_heavy/num_heavy;
if (num_night!=0)
avg_night=tot_night/num_night;
printf("Average speed of: light
vehicles: %5.1f mph\n", avg_light);
printf(" heavy vehicles: %5.1f mph\n", avg_heavy);
printf(" night travel: %5.1f mph\n", avg_night); }
2. [The original question]
Define a struct (class or
object type) with appropriate members, methods, and constructors, that can be
used to represent a point in two-dimensional space and perform useful
operations on such points, as described below. A point can be uniquely
represented by two numbers, its "x" and "y" coordinates.
Your definition should
include two constructors, one with the obvious parameters as illustrated below,
and one default (no-parameter) constructor. Your definition should also include
two methods, one which prints the point in the traditional manner, e.g.
(1.0,2.0) for a point whose x is 1 and whose y is 2, and one which simply
returns the distance between the point and some other point provided as a parameter.
Also include a method that
rotates a point 90 degrees clockwise (around the origin of course). If you are
not sure how the rotation is done, draw yourself a sketch showing the axes and
the point (x=1, y=2), and notice how it gets rotated to become (x=2, y=-1).
Your definition should work
if used with this piece of program:
{
point p(1.0, 2.0);
point origin(0.0, 0.0);
cout << "The point p is at
";
p.print();
cout << ", which is "
<< p.distance(origin)
<< " from the
origin\n";
p.rotate();
cout << "after rotation the point
p is at ";
p.print();
...
Now define a function (not a
method) that takes two points as parameters, and returns the distance between
them.
A closed shape (such as a
square or a hexagon) can be represented by an array of points specifying its
corners in clockwise order, in which the last point is the same as the first
one. For example, the snippet of code below creates the array T representing an
isosceles triangle pointing downwards, and prints it:
{
point T[] = { point(1,3), point(3,3), point(2,0), point(1,3) };
for (int i=0; i<4; i+=1)
T[i].print();
...
Define a function that takes
two parameters: an array of points (like T), and the number of points, and
returns the perimeter of the shape described by the array.
[End of question]
There
is very little to be said about anything in this question; it follows the
pattern you've seen in class many times.
The
question asked for a default constructor, but didn't tell you what it should
do. There are two equally reasonable alternatives. Either do nothing, or think
of a reasonable value that the components could be initialised to, and do it.
Both versions are shown below.
If
by some chance you have forgotten how to find the distance between two points,
it is just a matter of using Pythagoras' theorem: the length of the hypotenuse
is the square root of the sum of the squares of the lengths of the other two
sides.
The
rotation operation is also very simple. If you work out rotations properly, you
get this formula: newx=xcosa+ysina; newy=ycosa-xsina; for rotating the point
through angle "a" clockwise, and being engineering students, you
really should be able to handle something like that. However, you don't need
to. Rotation through a right angle is trivial. Just draw a couple of pictures
(as the question suggested) and you soon see that the rule is newx=oldy;
newy=-oldx.
The
perimeter of a shape is found by adding together the distances between
successive corners. Just be careful not to count the same side twice.
struct point
{ float x, y;
point(void);
point(float ix, float iy);
void print(void);
float distance(point p);
void rotate(void); };
point::point(void) // version one
{ }
point::point(void) // version two
{ x=0.0; y=0.0; }
point::point(float ix,
float iy)
{ x=ix; y=iy; }
void point::print(void)
{
printf("(%f,%f)", x, y); }
float
point::distance(point p)
{ float distx = p.x – x;
float disty = p.y – y;
return sqrt(distx*distx + disty*disty); }
void point::rotate(void)
{ float oldx = x;
x = y;
y = -oldx; }
// note that the remaining
definitions are plain functions, not methods.
// they are not defined
inside the struct, and their names do not begin
// with
"point::", but that doesn't stop them from using points.
float distance(point a,
point b) // version one
{ return a.distance(b); }
float distance(point a,
point b) // version two
{ float distx = a.x – b.x;
float disty = a.y – b.y;
return sqrt(distx*distx + disty*disty); }
float perimeter(point T[],
int np)
{ float total = 0.0;
for (int i=0; i<np-1; i+=1)
total += distance(T[i], T[i+1]);
return total; }