Homework #9

Homework #9#

Important

The file submission requirements are different than previous homeworks.

For each problem below, you need to submit a header file (.H) implementing the class described in the problem and a source file (.cpp) containing the main() function and any tests that the problem asks for.

Important

All work must be your own.

You may not use generative AI / large-language models for any part of this assignment.

  1. A simple class : Create a class called Rectangle that describes a rectangle. This should be done in a header file. Be sure to include a header guard. Your class should:

    • Have member data for the width and height of the rectangle

    • Implement two constructors for the class, one that takes a width and height, like:

      Rectangle r(1.0, 2.0);
      

      and one that is appropriate for a square, which just takes a single length and initializes both the width and height to that value, e.g.,

      Rectangle r(1.5);
      
    • Have member functions, perimeter(), that returns the perimeter of the rectangle and area(), that returns the area.

    • Have a third member function, is_square() that returns true if the Rectangle is a square.

    Next create a source file containing the main and show how to create:

    • a rectangle

    • a square

    and for each, output their properties (perimeter,area, and the result of the test if it is square) to the terminal.

    solution

    First the header:

    Listing 168 rectangle.H#
    #ifndef RECTANGLE_H
    #define RECTANGLE_H
    
    class Rectangle {
    
        double width;
        double height;
    
    public:
    
        Rectangle(double _width, double _height)
          : width(_width), height(_height)
        {}
    
        Rectangle(double _length)
          : width(_length), height(_length)
        {}
    
        double perimeter() {
            return 2 * width + 2 * height;
        }
    
        double area() {
            return width * height;
        }
    
        bool is_square() {
            return width == height;
        }
    };
    
    #endif
    

    Notice:

    • I am using a member initialization-list to initialize the data for the constructors. You could instead initialize it in the function body of the constructor.

    • since I used that class keyword, I need to explicitly make the constructors and member functions public.

    Now the driver:

    Listing 169 test_rectangle.cpp#
    #include <iostream>
    #include <format>
    
    #include "rectangle.H"
    
    
    int main() {
    
        Rectangle r(1.0, 2.0);
        std::cout << std::format("Rectangle: P = {}, A = {}, is_square = {}\n",
                                 r.perimeter(), r.area(), r.is_square());
        Rectangle s(3.0);
        std::cout << std::format("Square: P = {}, A = {}, is_square = {}\n",
                                 s.perimeter(), s.area(), s.is_square());
    
    }
    
  2. 3D vectors : Starting with our vector2d.H implementation (Example: Mathematical Vectors), extend it to three-dimensions (call your new implementation vector3d.H).

    You should:

    • Add z as member data.

    • Add a set_z function

    • Extend the addition (+), subtraction (-) and unary minus (the other -) operators to work for a 3D vector.

    • Update the output stream operator <<

    Don’t worry about the additional operators we looked at in class—just focus on our original implementation linked above.

    Now write a main() function that exercises each of these operators—use the driver we created for the 2D implementation as the starting point.

    solution

    First the header:

    Listing 170 vector3d.H#
    #ifndef VECTOR_3D_H
    #define VECTOR_3D_H
    
    #include <iostream>
    
    class Vector3d {
    
    private:
    
        // our member data
    
        double x;
        double y;
        double z;
    
    public:
    
        // default constructor
    
        Vector3d()
            : x{0.0}, y{0.0}, z{0.0}
        {}
    
        // another constructor
    
        Vector3d(double _x, double _y, double _z)
            : x{_x}, y{_y}, z{_z}
        {}
    
        // setters
    
        inline void set_x(double _x) {x = _x;}
    
        inline void set_y(double _y) {y = _y;}
    
        inline void set_z(double _z) {z = _z;}
    
        // operators
    
        // add two vectors
    
        Vector3d operator+(const Vector3d& vec) {
            return Vector3d(x + vec.x, y + vec.y, z + vec.z);
        }
    
        // subtract two vectors
    
        Vector3d operator-(const Vector3d& vec) {
            return Vector3d(x - vec.x, y - vec.y, z - vec.z);
        }
    
        // unary minus
    
        Vector3d operator-() {
            return Vector3d(-x, -y, -z);
        }
    
        // << is not a class member, but needs access to the member data
    
        friend std::ostream& operator<< (std::ostream& os, const Vector3d& v);
    };
    
    inline
    std::ostream& operator<< (std::ostream& os, const Vector3d& v)
    {
        os << "(" << v.x << ", " << v.y << ", " << v.z << ")";
        return os;
    }
    
    #endif
    

    The extension to 3-D is straightforward—we just need to include the new z component in all operations.

    Now the driver:

    Listing 171 test_vector3d.cpp#
    #include <iostream>
    #include "vector3d.H"
    
    int main() {
    
        // create 2 vectors
    
        Vector3d v1(1, 2, 3);
        Vector3d v2(2, 4, 6);
    
        // output our vectors
    
        std::cout << v1 << " " << v2 << std::endl;
    
        // output their sum
    
        std::cout << v1 + v2 << std::endl;
    
        // create a new vector from subtracting our two vectors
    
        auto v3 = v1 - v2;
        std::cout << v3 << std::endl;
    
        // create a copy
    
        auto v4 = v3;
        std::cout << v3 << " " << v4 << std::endl;
    
        // change the data in the original
        v3.set_x(0.0);
        v3.set_y(0.0);
        v3.set_z(0.0);
    
        // did both change? or just the original?
    
        std::cout << v3 << " " << v4 << std::endl;
    
    }
    
  3. Temperature logger : Let’s write a class called TemperatureLog that stores measurements of temperature at different times. Here’s how you should construct it:

    • The member data will be a vector of TemperatureReading, which is defined as:

      struct TemperatureReading {
          double time;
          double temp;
      };
      

      You can create this member data as:

      std::vector<TemperatureReading> log;
      
    • The constructor will take no arguments. You would create a log just as:

      TemperatureLog tlog;
      

      and it will hold no data initially.

    • A member function add_data will be used to add a (time, temperature) pair. You can have it take each quantity as a separate argument or write it to take a TemperatureReading object.

    Next you will add 3 member functions that act of the data:

    • The function mean() will return the average temperature.

    • The function max() will return the maximum temperature.

    • The function time_of_max() will return the time when the maximum temperature was reached.

    For these, you can use the ranges libraries or explicitly loop over the elements of log.

    Finally, write a driver / main() that creates a TemperatureLog and adds the following and adds the following data:

    time

    temperature

    10

    75

    15

    68

    22

    79

    40

    85

    49

    96

    55

    88

    62

    78

    70

    71

    76

    62

    and uses your functions to output:

    • the average temperature

    • the maximum temperature

    • the time of the maximum temperature

    solution

    First the header file. Here’s a version that uses the standard library algorithms:

    Listing 172 temperature_logger.H#
    #ifndef TEMPERATURE_LOGGER_H
    #define TEMPERATURE_LOGGER_H
    
    #include <vector>
    #include <numeric>
    #include <algorithm>
    
    struct TemperatureReading {
        double time;
        double temp;
    };
    
    struct TemperatureLog {
    
        std::vector<TemperatureReading> log;
    
        TemperatureLog() {}
    
        void add_data(double time, double temp) {
            log.push_back(TemperatureReading{.time=time, .temp=temp});
        }
    
        double mean() {
            auto sum =
                std::accumulate(log.begin(), log.end(),
                                0.0,
                                [] (const double a, const TemperatureReading b)
                                    {return a + b.temp;});
    
            return sum / static_cast<double>(log.size());
        }
    
        double max() {
            auto it =
                std::ranges::max_element(log,
                                         [] (const TemperatureReading& a,
                                             const TemperatureReading& b)
                                             {return a.temp < b.temp;});
            return (*it).temp;
        }
    
        double time_of_max() {
            auto max_temp = max();
            auto it = std::ranges::find_if(log, [=] (const TemperatureReading& a) {return a.temp == max_temp;});
            return (*it).time;
        }
    
    };
    
    #endif
    

    Some notes:

    • I use std::accumulate and std::ranges::max_element here. For std::ranges::max_element, we get an iterator, and need to dereference it to get the value it points to.

    • For the time_of_max function, I first call the max() function. Then I use std::ranges::find_if with a lambda function that looks for the element whose temperature matches the max temperature.

    If instead you wanted to explicitly loop over the elements of our log, here’s a version of the header that does that;

    Listing 173 temperature_logger_loops.H#
    #ifndef TEMPERATURE_LOGGER_H
    #define TEMPERATURE_LOGGER_H
    
    #include <vector>
    #include <limits>
    
    struct TemperatureReading {
        double time;
        double temp;
    };
    
    struct TemperatureLog {
    
        std::vector<TemperatureReading> log;
    
        TemperatureLog() {}
    
        void add_data(double time, double temp) {
            log.push_back(TemperatureReading{.time=time, .temp=temp});
        }
    
        double mean() {
            double sum{};
            for (auto tr : log) {
                sum += tr.temp;
            }
            return sum / static_cast<double>(log.size());
        }
    
        double max() {
            double max{std::numeric_limits<double>::lowest()};
            for (auto tr : log) {
                max = std::max(max, tr.temp);
            }
            return max;
        }
    
        double time_of_max() {
            auto max_temp = max();
            double time{};
            for (auto tr : log) {
                if (tr.temp == max_temp) {
                    time = tr.time;
                    break;
                }
            }
            return time;
        }
    
    };
    #endif
    

    Now the driver:

    Listing 174 test_temp_log.cpp#
    #include <iostream>
    
    #include "temperature_logger.H"
    
    
    int main() {
    
        TemperatureLog tlog;
    
        tlog.add_data(10, 75);
        tlog.add_data(15, 68);
        tlog.add_data(22, 79);
        tlog.add_data(40, 85);
        tlog.add_data(49, 96);
        tlog.add_data(55, 88);
        tlog.add_data(62, 78);
        tlog.add_data(70, 71);
        tlog.add_data(76, 62);
    
        std::cout << "mean temperature = " << tlog.mean() << std::endl;
        std::cout << "max temperature = " << tlog.max() << std::endl;
        std::cout << "time of max temperature = " << tlog.time_of_max() << std::endl;
    }