Skip to content

Instantly share code, notes, and snippets.

@dodheim
Last active July 4, 2020 18:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dodheim/7e7bf7b43d7dfcc48adad39db932660b to your computer and use it in GitHub Desktop.
Save dodheim/7e7bf7b43d7dfcc48adad39db932660b to your computer and use it in GitHub Desktop.
C++17 solution for /r/dailyprogrammer challenge #317 [intermediate]
#include <cstdint>
#include <type_traits>
#include <utility>
#include <optional>
#include <string_view>
#include <array>
#include <vector>
#include <boost/container/flat_map.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/spirit/home/x3/core.hpp>
#include <boost/spirit/home/x3/nonterminal/rule.hpp>
#include <boost/spirit/home/x3/operator.hpp>
#include <boost/spirit/home/x3/auxiliary/attr.hpp>
#include <boost/spirit/home/x3/auxiliary/eoi.hpp>
#include <boost/spirit/home/x3/char.hpp>
#include <boost/spirit/home/x3/numeric/uint.hpp>
#include <boost/spirit/home/x3/support/ast/variant.hpp>
namespace x3 = boost::spirit::x3;
struct alignas(4) element {
constexpr element(char const upper, char const lower) noexcept : chars_{{upper, lower}} { }
explicit constexpr operator std::string_view() const noexcept {
return {chars_.data()};
}
// HACK: std::array's operator <=> is missing as of MSVC 19.26; #include <compare> once fixed
friend bool operator <(element const& lhs, element const& rhs) noexcept {
return lhs.chars_ < rhs.chars_;
}
//friend constexpr auto operator <=>(element const&, element const&) noexcept = default;
private:
std::array<char, 3> chars_;
};
using compound = boost::container::flat_map<element, std::int_fast32_t>;
namespace ast {
struct component;
using formula = std::vector<component>;
struct allotrope {
char upper, lower;
std::int_fast32_t count;
constexpr operator element() const noexcept {
return {upper, lower};
}
};
struct compound {
formula components;
std::int_fast32_t multiplier;
};
struct component : x3::variant<allotrope, compound> {
using base_type::base_type;
using base_type::operator =;
};
struct component_visitor {
::compound& comp_;
using result_type = void;
result_type operator ()(allotrope const a) const noexcept {
comp_[a] += a.count;
}
result_type operator ()(compound const& comp) const noexcept {
::compound sub;
for (auto const& c : comp.components) {
c.apply_visitor(component_visitor{sub});
}
for (auto const& [elem, count] : sub) {
comp_[elem] += count * comp.multiplier;
}
}
};
}
BOOST_FUSION_ADAPT_STRUCT(ast::allotrope, upper, lower, count)
BOOST_FUSION_ADAPT_STRUCT(ast::compound, components, multiplier)
namespace parsers {
x3::rule<struct formula_rule, ast::formula> const formula;
x3::rule<struct allotrope_rule, ast::allotrope> const allotrope;
x3::rule<struct compound_rule, ast::compound> const compound;
const auto formula_def = +(compound | allotrope);
constexpr auto allotrope_def = x3::upper
>> (x3::lower | x3::attr(std::integral_constant<char, '\0'>{}))
>> (x3::uint16 | x3::attr(std::integral_constant<std::uint16_t, 1>{}));
const auto compound_def = '(' >> formula >> ')' >> x3::uint16;
BOOST_SPIRIT_DEFINE(formula, allotrope, compound)
}
constexpr auto parse_formula = [](std::string_view const input) -> std::optional<compound> {
std::optional<compound> ret;
if (ast::formula raw; x3::parse(input.cbegin(), input.cend(), parsers::formula >> x3::eoi, raw)) {
ast::component_visitor{ret.emplace()}(ast::compound{std::move(raw), 1});
}
return ret;
};
////////////////////////////////////////////////////////////////////////////////
// demo
#include <cstdlib>
#include <cstdio>
#include <exception>
#include <string>
#include <fmt/format.h>
using namespace std::string_view_literals;
constexpr auto pnp = [](std::string_view const input) -> void {
fmt::print("{}"sv, input);
if (auto const comp = parse_formula(input)) {
std::putchar('\n');
for (auto const& [elem, count] : *comp) {
fmt::print("{:>6}: {}\n"sv, elem, count);
}
} else {
std::puts(" - failed to parse");
}
std::putchar('\n');
};
constexpr auto stdin_getline = [](std::string& s) -> bool {
s.clear();
if (!s.capacity()) [[unlikely]] {
s.reserve(16);
}
auto* const cstdin = stdin;
for (auto len = s.size();;) {
if (std::ferror(cstdin) || std::feof(cstdin)) [[unlikely]] {
return false;
}
s.resize(s.capacity());
if (!std::fgets(s.data() + len, s.size() - len, cstdin)) [[unlikely]] {
s.erase(len);
return false;
}
len = s.find_last_not_of('\0');
bool const line_finished = s[len] == '\n';
if (!line_finished) {
++len;
}
s.erase(len);
if (line_finished) {
return true;
}
// relying on stdlib to apply growth factor to `reserve`; works as desired
// on vc++, libc++, libstdc++ but bad in theory and "should" be `resize`
s.reserve(s.capacity() + 1);
}
};
int main() try {
#ifdef _DEBUG
static constexpr std::array inputs{
"C6H12O6"sv,
"CCl2F2"sv,
"NaHCO3"sv,
"C4H8(OH)2"sv,
"PbCl(NH3)2(COOH)2"sv,
"PbCl(NH3(H2O)4)2"sv,
"Cl((NaH)2CO3)2"sv,
"PbCl(NH3)2((CO)2OH)2"sv,
"(C3(H2O)3)2"sv,
"FPb((NO4)2(COOH)3)4"sv
};
for (auto const& input : inputs) {
#else
for (std::string input; stdin_getline(input) && !input.empty();) {
if (auto const trimmed_len = input.find_last_not_of(" \t\r\f\v"sv) + 1; trimmed_len != input.size()) {
if (!trimmed_len) {
continue;
}
input.erase(trimmed_len);
}
#endif
pnp(input);
}
return EXIT_SUCCESS;
} catch (std::exception const& ex) {
std::fputs(ex.what(), stderr);
std::fputc('\n', stderr);
return EXIT_FAILURE;
} catch (...) {
std::fputs("unknown exception (...)\n", stderr);
return EXIT_FAILURE;
}
@dodheim
Copy link
Author

dodheim commented Jul 4, 2020

Revision 2 exorcises iostreams from the code in favor of fmtlib

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment