From 290126fd000b0239a583dcc5e55c19ec4d5230c1 Mon Sep 17 00:00:00 2001 From: Matthew Gretton-Dann Date: Wed, 8 Dec 2021 10:33:53 +0000 Subject: [PATCH] Cleanup 2021 day 8 puzzles --- 2021/puzzle-08-02.cc | 186 ++++++++++++++++++++++++++++++------------- 1 file changed, 129 insertions(+), 57 deletions(-) diff --git a/2021/puzzle-08-02.cc b/2021/puzzle-08-02.cc index 77aee86..55bb4a0 100644 --- a/2021/puzzle-08-02.cc +++ b/2021/puzzle-08-02.cc @@ -7,17 +7,74 @@ #include #include +using Segments = std::string; +using Segment = char; + +/** + * \brief If only one segment is lit return that segment, otherwise return '\0' + * \param segments Segments lit + * \return Lit segment if only one or '\0' othherwise. + */ +auto unique_segment(Segments const& segments) -> Segment +{ + if (segments.size() == 1) { + return segments[0]; + } + return '\0'; +} + +/** + * \brief Return unique segment lit when \a remove & \a rest are removed from \a segments + * \tparam Args Types of \a rest + * \param segments Segments initially lit + * \param remove Segment to remove from \a segments + * \param rest Any more segments to remove + * \return Unique lit segment or '\0' if there isn't one + */ +template +auto unique_segment(Segments const& segments, Segment remove, Args... rest) -> Segment; + +/** + * \brief Return unique segment lit when \a remove & \a rest are removed from \a segments + * \tparam Args Types of \a rest + * \param segments Segments initially lit + * \param remove Segment to remove from \a segments + * \param rest Any more segments to remove + * \return Unique lit segment or '\0' if there isn't one + */ +template +auto unique_segment(Segments const& segments, Segments const& remove, Args... rest) -> Segment +{ + std::string temp; + std::set_difference(segments.begin(), segments.end(), remove.begin(), remove.end(), + std::back_inserter(temp)); + return unique_segment(temp, rest...); +} + +template +auto unique_segment(Segments const& segments, Segment remove, Args... rest) -> Segment +{ + std::string temp; + std::set_difference(segments.begin(), segments.end(), &remove, &remove + 1, + std::back_inserter(temp)); + return unique_segment(temp, rest...); +} + auto main() -> int { std::string line; unsigned total{0}; while (std::getline(std::cin, line)) { + /* We want to identify the digits 1, 4, 7, 8 - which is easy as they're a unique number of lit + * segment2s. We also want to make use of 0, 6, and 9 which all have six segments lit - but + * we don't know which one matches which - so we keep them in a vector. + */ std::vector six_segments; - std::string one; - std::string four; - std::string seven; - std::string eight; + Segments one; + Segments four; + Segments seven; + Segments eight; auto it{line.begin()}; for (; *it != '|'; ++it) { if (*it == ' ') { @@ -49,64 +106,78 @@ auto main() -> int } } - /* Now decode: */ - char a{'.'}, b{'.'}, c{'.'}, d{'.'}, e{'.'}, f{'.'}, g{'.'}; - std::string temp; - std::set_difference(seven.begin(), seven.end(), one.begin(), one.end(), - std::back_inserter(temp)); - a = temp[0]; - std::string union47; - std::set_union(seven.begin(), seven.end(), four.begin(), four.end(), - std::back_inserter(union47)); - for (auto segment : six_segments) { - temp.clear(); - std::set_difference(segment.begin(), segment.end(), union47.begin(), union47.end(), - std::back_inserter(temp)); - if (temp.size() == 1) { - g = temp[0]; - temp.clear(); - std::set_difference(eight.begin(), eight.end(), segment.begin(), segment.end(), - std::back_inserter(temp)); - e = temp[0]; + /* Now decode: Each of these contains the character that maps to the given variable name. + * aa + * b c + * b c + * dd + * e f + * e f + * gg + */ + // Segment a{'\0'}; + Segment b{'\0'}; + // Segment c{'\0'}; + // Segment d{'\0'}; + Segment e{'\0'}; + Segment f{'\0'}; + Segment g{'\0'}; + + /* a is easy as it's the element lit in 7 but not in 1. */ + Segment a{unique_segment(seven, one)}; + + /* The digit 9 is the only six segment digit which has one segment lit when we remove the + * segments lit in 4 and 7. The remaining lit segment is g. (0 and 6 will both have e and g + * lit in this case). + * + * We can then detect e because it is the element lit in 8 which is not lit in 9. + */ + for (auto const& segments : six_segments) { + g = unique_segment(segments, four, seven); + if (g != '\0') { + e = unique_segment(eight, segments); // NOLINT(readability-suspicious-call-argument) break; } } - std::string union7ge{seven + g + e}; - std::sort(union7ge.begin(), union7ge.end()); - for (auto segment : six_segments) { - temp.clear(); - std::set_difference(segment.begin(), segment.end(), union7ge.begin(), union7ge.end(), - std::back_inserter(temp)); - if (temp.size() == 1) { - b = temp[0]; + + /* The digit 0 is the only six segment digit which has one segment lit which isn't in the digit + * 7 or is e or g. (6 and 9 have two segments in this case). This lit segment is b. + */ + for (auto const& segment : six_segments) { + b = unique_segment(segment, seven, g, e); + if (b != '\0') { + break; } } - std::string union1b{one + b}; - std::sort(union1b.begin(), union1b.end()); - temp.clear(); - std::set_difference(four.begin(), four.end(), union1b.begin(), union1b.end(), - std::back_inserter(temp)); - d = temp[0]; + /* Segment d is the remaining segment from 4 when 1 and b have been removed. */ + Segment d{unique_segment(four, one, b)}; - std::string unionabdeg{a, b, d, e, g}; - std::sort(unionabdeg.begin(), unionabdeg.end()); - for (auto segment : six_segments) { - temp.clear(); - std::set_difference(segment.begin(), segment.end(), unionabdeg.begin(), unionabdeg.end(), - std::back_inserter(temp)); - if (temp.size() == 1) { - f = temp[0]; + /* 6 is the six-segment digit with a, b, d, e, and g lit. (0 doesn't have d lit, 9 doesn't + * have e lit. The remaining segment in 6 is f. + */ + for (auto const& segment : six_segments) { + f = unique_segment(segment, a, b, d, e, g); + if (f != '\0') { + break; } } - temp.clear(); - std::string unionf{f}; - std::set_difference(one.begin(), one.end(), unionf.begin(), unionf.end(), - std::back_inserter(temp)); - c = temp[0]; + /* And finally c is the segment in 1 which isn't f. */ + Segment c{unique_segment(one, f)}; - std::array nums{ + /* Check everything worked (or at least found a result). */ + assert(a != '\0'); + assert(b != '\0'); + assert(c != '\0'); + assert(d != '\0'); + assert(e != '\0'); + assert(f != '\0'); + assert(g != '\0'); + + /* nums is an array of digits containing the segments which make up the display for that digit. + */ + std::array nums{ std::string{a, b, c, e, f, g}, // 0 std::string{c, f}, // 1 std::string{a, c, d, e, g}, // 2 @@ -119,17 +190,17 @@ auto main() -> int std::string{a, b, c, d, f, g} // 9 }; + /* Sort the digit lists into alphabetical order so checking becomes easier. */ for (auto& num : nums) { std::sort(num.begin(), num.end()); } - for (auto num : nums) { - std::cout << num << '\n'; - } - + /* Now work out the value for this line. */ unsigned value{0}; - for (++it; it != line.end(); it = (it == line.end() ? it : it + 1)) { + ++it; + while (it != line.end()) { if (*it == ' ') { + ++it; continue; } std::string s; @@ -137,7 +208,8 @@ auto main() -> int s += *it++; } std::sort(s.begin(), s.end()); - auto num{std::find(nums.begin(), nums.end(), s)}; + auto num{std::find(nums.begin(), // NOLINT(llvm-qualified-auto,readability-qualified-auto) + nums.end(), s)}; assert(num != nums.end()); value *= 10; value += num - nums.begin();