Answered step by step
Verified Expert Solution
Link Copied!

Question

00
1 Approved Answer

Overview The purpose of this assignment is to commence exploring C++ containers, iterators, and algorithms along with some other common/basic concepts (e.g., user-defined I/O) can

Overview

The purpose of this assignment is to commence exploring C++ containers, iterators, and algorithms along with some other common/basic concepts (e.g., user-defined I/O) can be used in modern C++ code.

Task

The task for this assignment is to have the following user-defined data type:

struct rgb

{

unsigned char red;

unsigned char green;

unsigned char blue;

};

be able to be:

  • read in from a stream (e.g., std::cin),
    • i.e., write: std::istream& operator >>(std::istream& is, rgb& colour); (see below)
  • written out to a stream (e.g., std::cout),
    • i.e., write: std::ostream& operator <<(std::ostream& os, rgb const& colour); (see below)
  • stored in a container,
    • e.g., std::vector, std::array; (see below)
  • processed via algorithms (and other functions) (e.g., std::transform).
    • e.g., write: double distance(rgb const& a, rgb const& b); (see below)

The rgb type represents a colour (e.g., for a pixel) with the components red, green, and blue. By convention, these components are values between 0 and 255 inclusively --but such is not enforced by the rgb definition in this assignment.

NOTE: In this assignment nothing will be added, defined, declared, or removed from the rgb structure. Many often think writing a class requires writing constructors, etc. which can be a lot of (often unnecessary) code. This assignment will rely on C++'s default definitions for constructors, etc. Subsequent assignments will define constructors, etc.

As with everything in this course, you cannot use the C language and C Standard Library unless that feature has not suitable C++ implementation, e.g., printf() and scanf() are not allowed to be used in this assignment.

Required Header Files

The required header files for this assignment are:

#include  // for std::sqrt #include  // for std::array #include  // for std::vector #include  // for std::numeric_limits #include  // for std::string #include  // for std::istream #include  // for std::ostream #include  // for std::cin, std::cout #include  // for std::transform using namespace std; // place this after the #includes 

Place these at the top of your program.

Defining The rgb Structure

Define the rgb structure mentioned at the top of this assignment next in your code, i.e.,

struct rgb { unsigned char red,

unsigned char green;

unsigned char blue; };

Again, don't worry that there is (intentionally) nothing else declared or defined within this structure: C++ will automatically create default, copy, move, constructors, a destructor, copy and move assignment operators as is required.

Writing std::istream& operator >>(std::istream& is, rgb& colour)

Define the operator overload of >>, i.e., std::istream& operator >>(std::istream& is, rgb& colour), as follows:

  • Read in an rgb structure into the colour parameter (i.e., the second parameter to this function).
    • The values are read in as whitespace-delimited numbers between 0 and 255.
    • The components will always appear in this order in the input stream: red, green, blue.
  • Regardless of how this function executes, its return statement is always return is; (i.e., the first std::istream& argument is returned).
  • If any component cannot be read in, then set the stream to "fail" with is.setstate(ios_base::failbit); and return from the function.
  • If any component cannot be read in, then ensure you never alter/update any values in colour (i.e., the second parameter to this function).
    • ADVICE: When reading in the components, declare them as local unsigned (not unsigned char) variables. (ASIDE: Ask yourself why one would do this.)

Write std::ostream& operator <<(std::ostream& os, rgb const& colour)

Define the operator overload of <<, i.e., std::ostream& operator <<(std::ostream& os, rgb const& colour), as follows:

  • Write out the contents of the provided rgb structure (i.e., colour --the second parameter to this function) to the output stream, os. Its components must be output in this order: red, green, blue. Also the components must be written out as unsigned (decimal) integers.
  • This function's return statement is always return os; (i.e., it returns the first parameter, os, to the function).
  • ADVICE:
    • Writing to os is just like writing values to std::cout, e.g., os << 34; would output the integer 34 to os.
    • It is legal and preferable when possible to write multiple values to os using more than one <<, e.g., os << 12 << ' ' << 24;
    • Notice that writing os << colour.red; will output a character --not an integer's numeric value. This is not desired --you are required to output an unsigned integer!
    • The C++ way to convert an unsigned char to an unsigned integer is to use static_cast, e.g., os << static_cast(colour.red);
      • NOTE: C-style casts are prohibited in this course as C++ has 4 special cast operators that should be used instead when needed. (ASIDE: For your information these casts are static_cast, reinterpret_cast, dynamic_cast, and const_cast.)

Testing Your Code So Far

You can now test your code, e.g., using a main() like this:

int main()

{

rgb value{}; // this declares an instance of the rgb structure

if (cin >> value)

cout << " Read successful. Writing: " << value << ' ';

}

You can delete this main() once your testing has been done. Sample input could be the numbers 5 34 130 --which should also be sucessfully output per the code example above.

Writing double distance(rgb const& a, rgb const& b)

This distance() function computes the distance between two colours, a and b. If you look at these two colours as a three-dimensional vector, computing the distance between the two colours is the same as computing the distance between two points:

It is easiest to compute the corresponding subtractions in three distinct variables first, then square those values, sum them, and finally compute the sqrt. You will need to use a floating-point type (e.g., double or float) for these values to properly compute the distance.

When done writing this function, test it to be sure it works properly.

Writing main()

In this assignment main() will be partially given to you and other parts of it you will have to write. Start by writing the following in main():

int main() { array const colours{{ { 0x00, 0x00, 0x00 }, // 0: black { 0x80, 0x00, 0x00 }, // 1: maroon { 0x00, 0x80, 0x00 }, // 2: green { 0x80, 0x80, 0x00 }, // 3: olive { 0x00, 0x00, 0x80 }, // 4: navy { 0x80, 0x00, 0x80 }, // 5: purple { 0x00, 0x80, 0x80 }, // 6: teal { 0xC0, 0xC0, 0xC0 }, // 7: silver { 0x80, 0x80, 0x80 }, // 8: grey { 0xFF, 0x00, 0x00 }, // 9: red { 0x00, 0xFF, 0x00 }, // 10: lime { 0xFF, 0xFF, 0x00 }, // 11: yellow { 0x00, 0x00, 0xFF }, // 12: blue { 0xFF, 0x00, 0xFF }, // 13: fushsia { 0x00, 0xFF, 0xFF }, // 14: aqua { 0xFF, 0xFF, 0xFF } // 15: white }}; array const colour_names{ "black", "maroon", "green", "olive", "navy", "purple", "teal", "silver", "gray", "red", "lime", "yellow", "blue", "fushsia", "aqua", "white" }; 

Some notes about the above code fragment:

  • std::array is an object that is a non-dynamically-allocated fixed-size array; std::vector is an object that is a dynamically-allocated growable array.
  • Most people familiar with C and/or C++ programming think of const being placed to the left of the type being made read-only. This only works for the simplest type declarations. In general, write const to the right of what is being made read only.
  • The { ... } code initializes the two arrays with those values. This feature was added in C++11. The syntax is somewhat peculiar only for std::array objects since, often, an extra set of { } is needed in the declaration, e.g., the colours declaration. Such is not already needed, however, e.g., the colour_names declaration. If you have issues, write the extra braces.
  • These colours and colour names are the official first 16 colours of the XWindows, and web colour palettes.

Earlier you wrote code to read in and write out rgb structure values. Start by writing a loop:

  • Loop reading in rgb structure values until an EOF or the stream becomes bad or is failed. The simplest way to do this is to use a for loop, e.g., for (rgb value{}; cin >> value; ) { ... }.

Within the loop, you will first want to:

  • Declare a std::vector holding double values called distances.
    • This will be used to store the calculated distances between the rgb value read in and the colours in the colours array.
    • This vector is intentionally not invoking any constructors, i.e., it is default constructed.
  • In the next statement, tell the distance vector to reserve sufficient memory to hold all to-be-computed distance values. (This will avoid the vector object from having to reallocate more memory and associated copying of data.) This is done by calling vector's reserve() function: distances.reserve(colours.size());
    • NOTE: This is very common to do when using std::vector, i.e., if you know how many elements your vector (or hash table) needs to hold, it is usually best to tell the object to avoid inefficiencies.

The distances vector is currently empty (i.e., distance.size() == 0). To populate the vector with distance values, you can use the std::transform algorithm to iterate through all rgb values in the colours array, appending the computed distance values to the distances vector by calling a (lambda) function. This code is very similar to transform() uses presented in the lectures:

  • Start by writing the call to transform, i.e., write transform(.
  • The first two arguments of transform() are the half-open input range for the colours array.
    • e.g., the first argument is the "beginning" of colours, the second argument is the "end" of colours (i.e., one-past-the-end).
  • The third argument to transform() must be an output iterator. Since you want to append elements and such is done using vector's push_back() function since a vector is a sequence container, this argument must be back_inserter(distances), i.e., note distances is the distances vector.
  • The fourth argument to transform() is the function that transforms an input element (from colours, i.e., an rgb value) to an output element (i.e., a double value that will be written to distances). The easiest way to do this is to write a lambda function that accepts an input value (i.e., an rgb value) and returns the compute output value, i.e., write this:
    • [&value](auto const& colour) { return distance(colour, value); }
    • where &value "captures" (by reference) the rgb value variable declared in the for loop above
    • NOTE: A lambda function cannot use local variables unless they are captured.

Finally you will want to determine the smallest distance between the rgb value read in to the variable value and the computed distances in the distances vector. Start by declaring a std::size_t (which is an unsigned int) integer to store the index of the colour with the smallest distance. This value should be invalid to start. An invalid-in-this-program value can be obtained from std::numeric_limits, i.e., the maximum value possible for the integer.

  • ASIDE: This is an "excuse" to make you aware of std::numeric_limits. Normally one would initialize index to colours.size() --which would be an invalid index.

This declaration should be written like this:

size_t index = std::numeric_limits::max(); 

Similarly, a variable is needed to store the current smallest distance value (which has to be found by iterating through the distances array). Start by setting the smallest distance value to the largest value that can be stored, e.g., assuming you are using double values to store your distances, this declaration would be:

double smallest_distance = std::numeric_limits::max(); 

Now, starting with the input range [begin(distances), end(distances), write a for loop that iterates through all elements (explicitly) updating index and smallest_distance when a distance is found that is smaller than the smallest_distance variable's value. Some help for this:

  • You can write the initialization condition of the for loop as: auto i=begin(distances), iEnd=end(distances); This is not unlike writing for (int i=1, j=2; etc.).
  • The for loop will only execute while i and iEnd are not equal.
  • The increment portion is ++i;
  • To access the distance value pointed to by i write *i (remember iterators are based on pointer syntax).
  • You can compute the current index from i by subtracting i from begin(distances), i.e., index = i - begin(distances);. This works because i is an random-access iterator (as are all iterators for vectors, arrays, and strings.)

The last step is to output the name of the closest matching colour if it was found, i.e.,

cout << colour_names[index] << ' '; 

But it is possible that index is invalid, so remember to write an if statement to check whether or not index is invalid first. If index is invalid, then do the following:

cout << "ERROR occurred. Aborting... ";

return 1;

Finally close the loop's brace and write the closing brace of main().

Sample Program Run

Here is a sample completed program run:

$ ./a2.exe 0 0 0 black 0 128 0  green 255 255 0 yellow 240 240 240 white $

Step by Step Solution

There are 3 Steps involved in it

Step: 1

blur-text-image

Get Instant Access with AI-Powered Solutions

See step-by-step solutions with expert insights and AI powered tools for academic success

Step: 2

blur-text-image

Step: 3

blur-text-image

Ace Your Homework with AI

Get the answers you need in no time with our AI-driven, step-by-step assistance

Get Started

Students also viewed these Databases questions