/*
=================================================================
Step 12 (no Solution for the optional TODO part is provided)
=================================================================
*/
#include "do_averages.h"
#include <sstream>
// std::istringstream
#ifndef TEST
int main() {
do_averages();
}
#else
#include "pxt.h"
void typical_good_input() {
{ ////////////////////////////////////////////////////////////
std::istringstream input{
"1 5 7" "\n"
"1.5 7" "\n"
"15.7" "\n"
};
std::ostringstream output{};
PX(do_averages(input, output), output.str())
>>= "4.33333" "\n"
"4.25" "\n"
"15.7" "\n";
} ////////////////////////////////////////////////////////////
}
void some_empty_inputs() {
{ ////////////////////////////////////////////////////////////
std::istringstream input{"\n"};
std::ostringstream output{};
PX(do_averages(input, output), output.str()) == "";
} ////////////////////////////////////////////////////////////
{ ////////////////////////////////////////////////////////////
std::istringstream input{" \n"};
std::ostringstream output{};
PX(do_averages(input, output), output.str()) == "";
} ////////////////////////////////////////////////////////////
}
void some_invalid_inputs() {
{ ////////////////////////////////////////////////////////////
std::istringstream input{
"1 5 7" "\n"
"1 S 7" "\n"
"15.7" "\n"
};
std::ostringstream output{};
PX(do_averages(input, output), output.str())
>>= "4.33333" "\n"
"non-numeric input - line ignored" "\n"
"15.7" "\n";
} ////////////////////////////////////////////////////////////
{ ////////////////////////////////////////////////////////////
std::istringstream input{
"1 5 7" "\n"
"xxx 7" "\n"
"1 xyz 7" "\n"
"1 xyz" "\n"
};
std::ostringstream output{};
PX(do_averages(input, output), output.str())
>>= "4.33333" "\n"
"non-numeric input - line ignored" "\n"
"non-numeric input - line ignored" "\n"
"non-numeric input - line ignored" "\n";
} ////////////////////////////////////////////////////////////
}
void fails_on_my_mac_only() {
{ ////////////////////////////////////////////////////////////
std::istringstream input{
"1 xyz" "\n"
"1 abc" "\n"
};
std::ostringstream output{};
PX(do_averages(input, output), output.str())
>>= "non-numeric input - line ignored" "\n"
"non-numeric input - line ignored" "\n";
} ////////////////////////////////////////////////////////////
}
int main() {
typical_good_input();
some_empty_inputs();
some_invalid_inputs();
// fails_on_my_mac_only();
}
#endif
# TODO STEP 12
[nothing more - this step is only provided as solution]
-----------------------------------------------------------------
TODO (optional - meant for further self-study only):
Here are some ideas for more programming exercises based what has
been achieved so far. These steps are mostly independent of each
other so you make "pick and chose" according to what matches your
interest best.
- Put every class and function developed as part of this code
- either into an anonymous namespace (if it only needs to be
visible in a single translation unit);
- or into the namespace `my` (if it needs to be visible across
translation units)
- Turn the helper function `do_single_average` into a local named
lambda defined inside `do_averages`.
- Generalize the `Averager` class into a "value processor" so
that instead of calculating averages other things may be done,
like eg. to determine the minimum and maximum value in a line.
- If you feel comfortable with writing template classes you may
generalize the type of values to be processed right from the
start, but it is easier to postpone the "templatization"
until a simple (non-template) class is available and only
prepare that step with a type alieas (eg. `value_type`).
- Define an interface `IValueProcessor` with the virtual
functions `operator+=` and `print`. (The latter is useful as
a helper for the implementation of `operator<<`).
The definition of the type alias can go here too.
- Have the current `Averager` implement that interface and now,
when everything is in place re-compile and run the tests.
- Review the current design under the point of view what is
still missing to fully generalize `do_averages` to process the
values in a line eg. in regard to their minima and maxima.
- The final goal is to add another argument to `do_averages`
that determines the "ValueProcessor" to be used, which can
be any class implementing the `IValueProcessor` interface.
- If you try to do that you will notice that this interface
needs to have another method, to query if there have been
some values seen.
- Also, if other ways of "value processing" are added, the
the functions `do_averages` and `do_single_averages` become
misnomers and should propably be renamed.
- Add a third argument of type `IValueProcessor&` to the
function once named `do_averages` and provide an `Averager`
object in the `main` (interactive and/or for automated testing).
- To demonstrate the independance from just doing the "average"
and another implementation of `IValueProcessor` to determine
minima and maxima of a line.
- Maybe test "interactive" only but preferably provide automated
tests for it … lots of tests.
- Based on an `std::vector<Averager>` "pivot" the current output:
- averages should be calculated in columns (instead per line)
- and a final line with the average (or the minima and maxima)
of each column added.
**Note:** This file will probably be extended with new ideas now
and then. You are welcome to return from time to time if you are
looking for more "programming challenges" based on this example.
SO STAY TUNED.
#include "do_averages.h"
#include "Averager.h"
#include <iostream>
// std::cin
// std::cout
// std::istream
// std::ostream
#include <sstream>
// std::istringstream
// std::ostringstream
#include <string>
// std::getline
// std::string
void do_single_average(std::string line, Averager& result) {
std::istringstream iss{line};
float value;
while (iss >> value)
result += value;
bool const received_some_values_but_not_seen_eol{
(result.getCount() > 0) && !iss.eof()
};
bool const fail_bit_is_set_but_eof_bit_is_not{
iss.fail() && !iss.eof()
};
if (received_some_values_but_not_seen_eol
|| fail_bit_is_set_but_eof_bit_is_not)
throw "non-numeric input";
}
void do_averages(std::istream& in, std::ostream& out) {
std::string line;
while (std::getline(in, line)) {
Averager sc{};
try {
do_single_average(line, sc);
}
catch (char const* errmsg) {
out << errmsg << " - line ignored" << std::endl;
continue;
}
if (sc.getCount() == 0) return;
out << sc << std::endl;
}
}
void do_averages() {
do_averages(std::cin, std::cout);
}
#ifndef PXT_off
#ifndef PXT_ostream
#define PXT_ostream std::cout
#endif
#define PX(...)\
PXT_namespace::PXT_class{__func__, __FILE__, __LINE__, #__VA_ARGS__, (__VA_ARGS__)}
#if defined(PXT_HAVE_BOOST_TYPEINDEX)\
|| defined(__has_include) && __has_include(<boost/type_index.hpp>)
#include <boost/type_index.hpp>
#define PT(...)\
PXT_namespace::PXT_helper(__func__, __FILE__, __LINE__, #__VA_ARGS__,\
boost::typeindex::type_id_with_cvr<__VA_ARGS__>())
#endif
#if !defined(PX)\
|| !defined(PT)
struct PXT_dummy{
template<typename T> void operator==(T const&) const {}
template<typename T> void operator*=(T const&) const {}
template<typename T> void operator>>=(T const&) const {}
template<typename T> void operator%=(T const&) const {}
};
#if !defined(PX)
#define PX(...) PXT_dummy{}
#endif
#if !defined(PT)
#define PT(...) PXT_dummy{}
#endif
#endif
#include <sstream>
#include <iostream>
namespace PXT_namespace {
class PXT_class {
bool done_{};
char const* func_;
char const* file_;
int const line_{};
char const* texpr_;
std::stringstream streamed_result{};
std::stringstream expected_result{};
std::ostream& source_location() const;
void plain_output();
void two_line_compare();
void multi_line_compare();
void glob_pattern_compare();
struct Result {
unsigned long long total;
unsigned long long failed;
~Result();
};
static Result counter;
public:
static std::ostream& out_stream;
static bool ignore_expecations;
template<typename T>
PXT_class(char const* func, char const* file, int line, char const* texpr, T const& expr)
: func_{func}, file_{file}, line_{line}, texpr_{texpr}
{
streamed_result.copyfmt(out_stream);
streamed_result << expr;
}
~PXT_class() {
if (not done_) plain_output();
}
template<typename T>
void operator==(T const& expr) {
expected_result.copyfmt(streamed_result);
expected_result << expr;
two_line_compare();
}
void operator>>=(std::string lines) {
expected_result << lines;
multi_line_compare();
}
void operator*=(std::string smatch) {
glob_pattern_compare();
}
};
} // namespace
#ifdef PXT_header_only
#include "pxt.cpp"
#endif
#endif
#include <iosfwd>
void do_averages(std::istream&, std::ostream&);
void do_averages();
#ifndef PXT_header_only
#include "pxt.h"
#endif
#include <iostream>
namespace PXT_namespace {
std::ostream& PXT_class::out_stream{PXT_ostream};
PXT_class::Result PXT_class::counter{0, 0};
PXT_class::Result::~Result() {
if (failed > 0) {
PXT_class::out_stream << "** " << failed << " of "
<< total << " tests failed **" << std::endl;
}
else if (total > 0) {
PXT_class::out_stream << "** ALL " << total << " TESTS PASSED **" << std::endl;
}
}
std::ostream& PXT_class::source_location() const {
return out_stream << file_ << ' ' << '[' << func_ << ':' << line_ << ']' << ' ' << texpr_;
}
void PXT_class::plain_output() {
source_location() << " == " << streamed_result.str() << std::endl;
}
void PXT_class::two_line_compare() {
done_ = true;
++counter.total;
if (streamed_result.str() != expected_result.str()) {
++counter.failed;
source_location() << '\n'
<< " = received: " << streamed_result.str() << '\n'
<< " ! expected: " << expected_result.str() << std::endl;
}
}
void PXT_class::multi_line_compare() {
std::string streamed_line{};
std::string expected_line{};
std::ostringstream common{};
while (std::getline(streamed_result, streamed_line),
(expected_result >> std::ws),
std::getline(expected_result, expected_line),
!streamed_result.eof()
&& !expected_result.eof()
&& (streamed_line == expected_line)) {
common << " = " << expected_line << '\n';
}
done_ = true;
++counter.total;
if (streamed_result.eof()
&& expected_result.eof()) return; // no difference
++counter.failed;
if (streamed_result.eof()) { // less streamed than expected
source_location() << '\n'
<< common.str()
<< " + expected: " << expected_line << std::endl;
return;
}
if (expected_result.eof()) { // less expected than streamed
source_location() << '\n'
<< common.str()
<< " + received: " << streamed_line << std::endl;
return;
}
source_location() << '\n' // difference in streamed to expected
<< common.str()
<< " ~ received: " << streamed_line << '\n'
<< " ~ expected: " << expected_line << std::endl;
}
} // namespace
#include "Averager.h"
#include <iostream>
namespace my {
std::ostream& operator<<(std::ostream& lhs, Averager const& rhs) {
return lhs << rhs.sum_/rhs.count_;
}
void operator+=(Averager& lhs, float rhs) {
lhs.sum_ += rhs;
++lhs.count_;
}
}
#ifndef MY_AVERAGER_H_
#define MY_AVERAGER_H_
#include <iosfwd>
class Averager {
float sum_{0.0f};
int count_{0};
friend
std::ostream& operator<<(std::ostream&, Averager const& rhs);
friend
void operator+=(Averager& lhs, float rhs);
public:
auto getCount() const { return count_; }
};
#endif // include guard