[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[lmi-commits] [lmi] master 88138c0 1/2: Add and unit-test input-sequence
From: |
Greg Chicares |
Subject: |
[lmi-commits] [lmi] master 88138c0 1/2: Add and unit-test input-sequence canonical form [396] |
Date: |
Fri, 24 Feb 2017 22:53:51 -0500 (EST) |
branch: master
commit 88138c0826411056085ca512d47f09fcc5bcff96
Author: Gregory W. Chicares <address@hidden>
Commit: Gregory W. Chicares <address@hidden>
Add and unit-test input-sequence canonical form [396]
Updated the unit-test expected results by mechanically transforming
value [begin, end);
to
value end;
---
input_sequence.cpp | 123 ++++++++++++++++++++++++++++++++++++++++++++++++
input_sequence.hpp | 2 +
input_sequence_test.cpp | 48 ++++++++-----------
3 files changed, 145 insertions(+), 28 deletions(-)
diff --git a/input_sequence.cpp b/input_sequence.cpp
index a79b842..162451c 100644
--- a/input_sequence.cpp
+++ b/input_sequence.cpp
@@ -238,6 +238,129 @@ void InputSequence::initialize_from_vector(std::vector<T>
const& v)
InputSequence::~InputSequence() = default;
+/// Canonical form of an input sequence
+///
+/// Consider the set S of all strings that are well formed wrt the
+/// grammar defined by class SequenceParser. Some are equivalent:
+/// e.g., "1 [0, maturity)" and "1" mean the same thing. Choosing one
+/// member of each equivalence class defines a canonical form.
+///
+/// The canonical form need not be a string if a better representation
+/// can be found. For example, it might appear obvious that any member
+/// of S can be transformed into this member of the present class:
+/// std::vector<ValueInterval> intervals_;
+/// yet that is not necessarily the case: this sequence
+/// 0, @65; 10000
+/// is well formed, but (as this is written in 2017-02) would not be
+/// permitted for a 70-year-old. Even if that obstacle is overcome,
+/// a std::vector<ValueInterval> seems a poor choice because it is
+/// intricate and unwieldy: it would be uncouth to serialize that into
+/// xml or ask humans to deal with it. A simple, compact, readable
+/// string is wanted instead.
+///
+/// Another unsuccessful candidate for the canonical form would have
+/// specified left-closed and right-open intervals in full. Thus,
+/// 0, retirement; 10000, #10; 0
+/// would have been canonicalized as
+/// 0 [0, retirement); 10000 [retirement, #10); 0 [?, maturity)
+/// But then the last interval must begin at "retirement + 10", which
+/// is not allowed by the grammar and therefore cannot be canonical.
+/// At first, it had seemed possible to work around this by changing
+/// the parser, to forbid
+/// e_number_of_years == ValueInterval.begin_mode
+/// by replacing that mode with the most recent differing begin_mode:
+/// thus, {"@50" + "#10"} and {"5" + "#10"} would become "@60" and 15
+/// respectively. However, that experiment failed because "retirement"
+/// is neither an age nor a duration. It might be deemed to signify an
+/// age, but that would lose the variable nature of the retirement-age
+/// ctor argument, inaptly treating the case above as equivalent to
+/// 0, retirement; 10000, @C; 0
+/// where C is 75 for retirement at age 65, 72...at age 62, and so on.
+/// Specifying the original string
+/// 0, retirement; 10000, #10; 0
+/// at the group level, for a census with differing retirement ages,
+/// encompasses that variation; no canonicalization that loses that
+/// advantage is acceptable.
+
+std::string InputSequence::canonical_form() const
+{
+ std::ostringstream oss;
+ for(auto const& interval_i : intervals_)
+ {
+ if(interval_i.value_is_keyword)
+ {
+ oss << interval_i.value_keyword;
+ }
+ else
+ {
+ oss << value_cast<std::string>(interval_i.value_number);
+ }
+
+ if(1 == intervals_.size())
+ {
+ break;
+ }
+
+ std::string s;
+ switch(interval_i.end_mode)
+ {
+ case e_invalid_mode:
+ {
+ fatal_error() << "Invalid mode." << LMI_FLUSH;
+ }
+ break;
+ case e_duration:
+ {
+ int const z = interval_i.end_duration;
+ s = " " + value_cast<std::string>(z);
+ }
+ break;
+ case e_attained_age:
+ {
+ int const z = interval_i.end_duration + issue_age_;
+ s = " @" + value_cast<std::string>(z);
+ }
+ break;
+ case e_number_of_years:
+ {
+ int const z = interval_i.end_duration -
interval_i.begin_duration;
+ s = " #" + value_cast<std::string>(z);
+ }
+ break;
+ case e_inception:
+ {
+ fatal_error() << "Interval ended at inception." << LMI_FLUSH;
+ }
+ break;
+ case e_inforce:
+ {
+ fatal_error() << "'e_inforce' not implemented." << LMI_FLUSH;
+ }
+ break;
+ case e_retirement:
+ {
+ s = " retirement";
+ }
+ break;
+ case e_maturity:
+ {
+ s = " maturity"; // Generally omitted.
+ }
+ break;
+ }
+
+ if(interval_i.end_duration != years_to_maturity_)
+ {
+ oss << s << "; ";
+ }
+ else
+ {
+ ; // Do nothing.
+ }
+ }
+ return oss.str();
+}
+
std::vector<double> const& InputSequence::linear_number_representation() const
{
return number_result_;
diff --git a/input_sequence.hpp b/input_sequence.hpp
index c2b3a83..ba586e5 100644
--- a/input_sequence.hpp
+++ b/input_sequence.hpp
@@ -161,6 +161,8 @@ class LMI_SO InputSequence
~InputSequence();
+ std::string canonical_form() const;
+
std::vector<double> const& linear_number_representation() const;
std::vector<std::string> const& linear_keyword_representation() const;
diff --git a/input_sequence_test.cpp b/input_sequence_test.cpp
index 9ceacaa..57ea721 100644
--- a/input_sequence_test.cpp
+++ b/input_sequence_test.cpp
@@ -77,7 +77,7 @@ void check
std::cout << std::endl;
}
- std::string const& f = seq.mathematical_representation();
+ std::string const& f = seq.canonical_form();
bool const bf = g == f;
if(!bf)
{
@@ -87,13 +87,7 @@ void check
std::cout << std::endl;
}
-//#define TEST_REPRESENTATION
-#if defined TEST_REPRESENTATION
INVOKE_BOOST_TEST(bv && bs && bf, file, line);
-#else // !defined TEST_REPRESENTATION
- // Don't make 'bf' fail the test yet.
- INVOKE_BOOST_TEST(bv && bs /* && bf */, file, line);
-#endif // !defined TEST_REPRESENTATION
}
catch(std::exception const& x)
{
@@ -178,7 +172,7 @@ int test_main(int, char*[])
std::string const e("1 3; 7 5;0");
census += e + "\t\t\t\n";
// '1 3; 7 5; 0'
- std::string const g("1 [0, 3); 7 [3, 5); 0 [5, maturity)");
+ std::string const g("1 3; 7 5; 0");
check(__FILE__, __LINE__, n, d, e, g);
}
@@ -190,7 +184,7 @@ int test_main(int, char*[])
std::string const e("1; 2; 3");
census += e + "\t\t\t\n";
// '1 1; 2 2; 3'
- std::string const g("1 [0, 1); 2 [1, 2); 3 [2, maturity)");
+ std::string const g("1 1; 2 2; 3");
check(__FILE__, __LINE__, n, d, e, g);
}
@@ -201,7 +195,7 @@ int test_main(int, char*[])
std::string const e("1 3; 3 6; 5 9; 7");
census += e + "\t\t\t\n";
// '1 3; 3 6; 5 9; 7'
- std::string const g("1 [0, 3); 3 [3, 6); 5 [6, 9); 7 [9, maturity)");
+ std::string const g("1 3; 3 6; 5 9; 7");
check(__FILE__, __LINE__, n, d, e, g);
}
@@ -212,7 +206,7 @@ int test_main(int, char*[])
std::string const e("1 @93; 3 @96; 5 @99; 7");
census += e + "\t\t\t\n";
// '1 @93; 3 @96; 5 @99; 7'
- std::string const g("1 [0, @93); 3 address@hidden, @96); 5 address@hidden,
@99); 7 address@hidden, maturity)");
+ std::string const g("1 @93; 3 @96; 5 @99; 7");
check(__FILE__, __LINE__, n, d, e, g);
}
@@ -223,7 +217,7 @@ int test_main(int, char*[])
std::string const e("1 #3; 3 #3; 5 #3; 7");
census += e + "\t\t\t\n";
// '1 #3; 3 #3; 5 #3; 7'
- std::string const g("1 [0, #3); 3 [3, #3); 5 [6, #3); 7 [9, maturity)");
+ std::string const g("1 #3; 3 #3; 5 #3; 7");
check(__FILE__, __LINE__, n, d, e, g);
}
@@ -236,7 +230,7 @@ int test_main(int, char*[])
std::string const e("1 [0, 2); 3 [2, 5); 5 [5, 6); 7");
census += e + "\t\t\t\n";
// '1 2; 3 5; 5 6; 7'
- std::string const g("1 [0, 2); 3 [2, 5); 5 [5, 6); 7 [6, maturity)");
+ std::string const g("1 2; 3 5; 5 6; 7");
check(__FILE__, __LINE__, n, d, e, g);
}
@@ -248,7 +242,7 @@ int test_main(int, char*[])
census += e + "\t\t\t\n";
// '1 1; 1 3; 3 6; 5 7; 7'
// Should the first two intervals be combined?
- std::string const g("1 [0, 1); 1 [1, 3); 3 [3, 6); 5 [6, 7); 7 [7,
maturity)");
+ std::string const g("1 1; 1 3; 3 6; 5 7; 7");
check(__FILE__, __LINE__, n, d, e, g);
}
@@ -259,7 +253,7 @@ int test_main(int, char*[])
std::string const e("1 [0, 4); 2 5; 3 #1; 4 @97; 5");
census += e + "\t\t\t\n";
// '1 4; 2 5; 3 #1; 4 @97; 5'
- std::string const g("1 [0, 4); 2 [4, 5); 3 [5, #1); 4 [6, @97); 5
address@hidden, maturity)");
+ std::string const g("1 4; 2 5; 3 #1; 4 @97; 5");
check(__FILE__, __LINE__, n, d, e, g);
}
@@ -270,7 +264,7 @@ int test_main(int, char*[])
std::string const e("1 [0, 1); 3 [1, 2); 5 (1, 2]; 7");
census += e + "\t\t\t\n";
// '1 1; 3 2; 5 3; 7'
- std::string const g("1 [0, 1); 3 [1, 2); 5 [2, 3); 7 [3, maturity)");
+ std::string const g("1 1; 3 2; 5 3; 7");
check(__FILE__, __LINE__, n, d, e, g);
}
@@ -330,7 +324,7 @@ int test_main(int, char*[])
std::string const e("1 [1, 2); 3 [3, 3]; 5 (4, 5]; 7");
census += e + "\t\t\t\n";
// '0 1; 1 2; 0 3; 3 4; 0 5; 5 6; 7'
- std::string const g("0 [0, 1); 1 [1, 2); 0 [2, 3); 3 [3, 4); 0 [4, 5); 5
[5, 6); 7 [6, maturity)");
+ std::string const g("0 1; 1 2; 0 3; 3 4; 0 5; 5 6; 7");
check(__FILE__, __LINE__, n, d, e, g);
}
@@ -340,7 +334,7 @@ int test_main(int, char*[])
double const d[n] = {0, 1, 1, 3, 3, 5, 5, 7, 7};
std::string const e("0; 1 (0, 8]; 3 (2, 7]; 5 (4, 6]; 7");
// census: invalid expression cannot be pasted into GUI
- std::string const g("0 [0, 1); 1 [1, 9); 3 [3, 8); 5 [5, 7); 7 [7,
maturity)");
+ std::string const g("0 1; 1 9; 3 8; 5 7; 7");
char const* m =
"Interval [ 9, 3 ) is improper: it ends before it begins."
;
@@ -372,7 +366,7 @@ int test_main(int, char*[])
std::string const e("12 [1, @92); 27 address@hidden, @93]; 1 (@94, #1];
7");
census += e + "\t\t\t\n";
// '0 1; 12 @92; 0 @93; 27 @94; 0 @95; 1 #1; 7'
- std::string const g("0 [0, 1); 12 [1, @92); 0 address@hidden, @93); 27
address@hidden, @94); 0 address@hidden, @95); 1 address@hidden, #1); 7
address@hidden, maturity)");
+ std::string const g("0 1; 12 @92; 0 @93; 27 @94; 0 @95; 1 #1; 7");
check(__FILE__, __LINE__, n, d, e, g);
}
@@ -385,12 +379,10 @@ int test_main(int, char*[])
std::string const e("12.25 [1,@92); 27.875 address@hidden,@93];
1.0625(@94,#1]; 7.5");
census += e + "\t\t\t\n";
// '0 1; 12.25 @92; 0 @93; 27.875 @94; 0 @95; 1.0625 #1; 7.5'
- std::string const g("0 [0, 1); 12.25 [1, @92); 0 address@hidden, @93);
27.875 address@hidden, @94); 0 address@hidden, @95); 1.0625 address@hidden,
#1); 7.5 address@hidden, maturity)");
+ std::string const g("0 1; 12.25 @92; 0 @93; 27.875 @94; 0 @95; 1.0625 #1;
7.5");
check(__FILE__, __LINE__, n, d, e, g);
}
- // TODO ?? Test against canonical representation once we define that.
-
// Test construction from numeric vector.
{
std::vector<double> const v{1, 1, 1, 2, 2};
@@ -434,7 +426,7 @@ int test_main(int, char*[])
std::string const e("p[0, 2); rrr [2, 4);q[4, 6);");
census += "glp[0, 2); target [2, 4);gsp[4, 6);\t\t\t\n";
// 'glp 2; target 4; gsp'
- std::string const g("p [0, 2); rrr [2, 4); q [4, maturity)");
+ std::string const g("p 2; rrr 4; q");
strvec const k{"not_used", "p", "q", "r", "rr", "rrr"};
check(__FILE__, __LINE__, n, d, e, g, "", k, c);
// Toggle keywords-only switch on: same result.
@@ -454,7 +446,7 @@ int test_main(int, char*[])
std::string const e("1 [0, 2); keyword_00 [2, 4); 5 [4, 6); 7");
census += "1 [0, 2); corridor [2, 4); 5 [4, 6); 7\t\t\t\n";
// '1 2; corridor 4; 5 6; 7'
- std::string const g("1 [0, 2); keyword_00 [2, 4); 5 [4, 6); 7 [6,
maturity)");
+ std::string const g("1 2; keyword_00 4; 5 6; 7");
strvec const k{"keyword_00"};
check(__FILE__, __LINE__, n, d, e, g, "", k, c);
}
@@ -517,7 +509,7 @@ int test_main(int, char*[])
std::string const e("q [0, 2); p [4, maturity)");
census += "\tquarterly [0, 2); monthly [4, maturity)\t\t\n";
// 'quarterly 2; annual 4; monthly'
- std::string const g("q [0, 2); z [2, 4); p [4, maturity)");
+ std::string const g("q 2; z 4; p");
strvec const k{"p", "q", "z"};
bool const o = true;
std::string w("z");
@@ -560,7 +552,7 @@ int test_main(int, char*[])
std::string const e("q [1, 3); p [3, maturity)");
census += "\tquarterly [1, 3); monthly [3, maturity)\t\t\n";
// 'annual 1; quarterly 3; monthly'
- std::string const g("z [0, 1); q [1, 3); p [3, maturity)");
+ std::string const g("z 1; q 3; p");
strvec const k{"p", "q", "z"};
bool const o = true;
std::string w("z");
@@ -579,7 +571,7 @@ int test_main(int, char*[])
std::string const e("q [1, 3); p [3, maturity)");
census += "sevenpay [1, 3); glp [3, maturity)\t\t\t\n";
// '0 1; sevenpay 3; glp'
- std::string const g("0 [0, 1); q [1, 3); p [3, maturity)");
+ std::string const g("0 1; q 3; p");
strvec const k{"p", "q", "z"};
check(__FILE__, __LINE__, n, d, e, g, "", k, c);
}
@@ -591,7 +583,7 @@ int test_main(int, char*[])
std::string const e("7, retirement; 4, maturity");
census += e + "\t\t\t\n";
// '7 retirement; 4'
- std::string const g("7 [0, retirement); 4 [retirement, maturity)");
+ std::string const g("7 retirement; 4");
check(__FILE__, __LINE__, n, d, e, g);
InputSequence const seq(e, 10, 90, 95, 0, 2002);
std::vector<ValueInterval> const& i(seq.interval_representation());