Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #include "pch.h"
- #include <algorithm>
- #include <charconv>
- #include <iostream>
- #include <iterator>
- #include <limits>
- #include <numeric>
- #include <ranges>
- #include <span>
- #include <stdexcept>
- #include <string>
- #include <string_view>
- using namespace std::string_view_literals;
- using namespace std::string_literals;
- using std::string_view;
- using StringViews = std::vector<string_view>;
- using Number = int;
- using Numbers = std::vector<Number>;
- constexpr auto DELIMITER = ","sv;
- constexpr auto COMMENT = "//"sv;
- const auto HELP_MSG_ON_NEGATIVE_INPUT = "negatives not allowed: "s;
- constexpr string_view WHITE_SPACE = " \f\n\r\t\v"sv;
- constexpr auto positives = [](auto n) noexcept { return n >= 0; };
- constexpr auto negatives = [](auto n) noexcept { return n < 0; };
- constexpr auto thousands = [](auto n) noexcept { return n > 999; };
- [[nodiscard]] Number from_chars(string_view s) {
- Number value;
- auto res = std::from_chars(s.data(), s.data() + s.size(), value);
- // These two exceptions reflect the behavior of std::stoi.
- if (res.ec == std::errc::invalid_argument) {
- throw std::invalid_argument("invalid_argument");
- } else if (res.ec == std::errc::result_out_of_range) {
- throw std::out_of_range("out_of_range");
- }
- return value;
- };
- //TODO: restrict to container types / ranges.
- [[nodiscard]] Numbers from_chars(auto v) {
- Numbers results;
- results.reserve(v.size());
- for (auto &sv : v) {
- if (sv.empty()) {
- continue;
- }
- results.push_back(from_chars(sv));
- }
- return results;
- }
- string_view trimLeft(string_view in, string_view delims = WHITE_SPACE) noexcept {
- const auto countFromTheLeft = in.find_first_not_of(delims);
- in.remove_prefix(countFromTheLeft);
- return in;
- };
- string_view trimRight(string_view in, string_view delims = WHITE_SPACE) noexcept {
- const auto lastValidPos = in.find_last_not_of(delims);
- const auto countFromTheRight = in.size() - lastValidPos - 1;
- in.remove_suffix(countFromTheRight);
- return in;
- };
- string_view trim(string_view in, string_view delims = WHITE_SPACE) noexcept {
- return trimRight(trimLeft(in));
- }
- size_t count(std::string_view str, std::string_view sub) {
- if (sub.empty()) {
- return 0;
- }
- size_t count = 0;
- size_t pos = 0;
- while ((pos = str.find(sub, pos)) != std::string_view::npos) {
- ++count;
- pos += sub.length(); // Move past the current occurrence
- }
- return count;
- }
- /*[[nodiscard]] StringViews split(string_view source, string_view delim) {
- StringViews values;
- std::ranges::split_view outer_view{source, delim};
- for (const auto &split : outer_view) {
- if (split.empty()) {
- continue;
- }
- values.emplace_back(split.begin(), split.end());
- }
- return values;
- }*/
- [[nodiscard]] StringViews split(std::string_view in, std::string_view sep) {
- std::vector<std::string_view> result;
- if (sep.empty()) {
- result.emplace_back(in);
- return result;
- }
- result.reserve(count(in, sep) + 1);
- size_t startingFrom = 0;
- size_t end;
- while ((end = in.find(sep, startingFrom)) != std::string_view::npos) {
- result.emplace_back(in.substr(startingFrom, end - startingFrom));
- startingFrom = end + sep.size();
- }
- result.emplace_back(in.substr(startingFrom));
- return result;
- }
- [[nodiscard]] Number sum(auto values) noexcept {
- return std::reduce(std::begin(values), std::end(values));
- }
- [[nodiscard]] bool empty(auto begin, auto end) {
- return std::distance(begin, end) == 0;
- }
- [[nodiscard]] std::string replace(string_view haystack, string_view from,
- string_view to) {
- auto clean = std::string(haystack);
- if (from.empty()) {
- return clean;
- }
- size_t pos = 0;
- size_t offset = 0;
- while ((pos = clean.find(from, offset)) != std::string::npos) {
- clean.replace(pos, from.size(), to);
- offset = pos + to.size();
- }
- return clean;
- }
- // overloads to let to_string handle non-numerics
- std::string to_string(const std::string &value) noexcept { return value; }
- std::string to_string(string_view value) noexcept { return std::string(value); }
- std::string join(std::input_iterator auto begin, std::input_iterator auto end,
- const std::string delim = ", "s) {
- if (empty(begin, end)) {
- return {};
- }
- using namespace std;
- // std::accumulate, until we get std::experimental::make_ostream_joiner, or
- // std::format::join
- return std::accumulate(begin + 1, end, to_string(*begin),
- [&delim](const std::string &a, auto b) {
- return a + delim + to_string(b);
- });
- }
- std::string join(auto container, std::string delim = ", "s) {
- return join(std::begin(container), std::end(container), delim);
- }
- void filter(Numbers &v, auto predicate) noexcept {
- std::erase_if(v, predicate);
- }
- bool contains(Numbers &v, auto predicate) {
- return std::ranges::find_if(v, predicate) != v.end();
- }
- void throwBadArguments(Numbers &v) {
- auto subrange =
- std::ranges::stable_partition(v, positives);
- auto invalidValues = join(subrange.begin(), subrange.end());
- throw std::invalid_argument(HELP_MSG_ON_NEGATIVE_INPUT +
- std::move(invalidValues));
- }
- string_view findDelimeter(string_view &s) noexcept {
- if (s.starts_with(COMMENT) == false) {
- return DELIMITER;
- }
- s.remove_prefix(s.find_first_not_of(COMMENT));
- const auto delimeterLength = s.find_first_of("0123456789");
- return s.substr(0, delimeterLength);
- }
- struct StringCalculator {
- Numbers parseNumbers(string_view s) const {
- if (s.empty()) {
- return {};
- }
- const auto delim = findDelimeter(s);
- const auto clean = replace(s, "\n", delim);
- const auto strings = split(clean, delim);
- return from_chars(strings);
- }
- Number add(string_view s) const {
- auto numbers = parseNumbers(s);
- if (contains(numbers, negatives)) {
- throwBadArguments(numbers);
- }
- filter(numbers, thousands);
- return sum(numbers);
- }
- };
- TEST(StringCalculator, ReturnsZeroOnEmpty) {
- const StringCalculator c;
- EXPECT_EQ(c.add(""), 0);
- };
- TEST(StringCalculator, ReturnsSameOnSingleNumber) {
- const StringCalculator c;
- EXPECT_EQ(c.add("1"), 1);
- EXPECT_EQ(c.add("9"), 9);
- EXPECT_EQ(c.add("878"), 878);
- };
- TEST(StringCalculator, ThrowsOnBadInput) {
- const StringCalculator c;
- EXPECT_THROW(c.add("p"), std::invalid_argument);
- constexpr std::size_t largerThanNumber =
- std::numeric_limits<Number>::max() + std::size_t(1);
- EXPECT_THROW(c.add(std::to_string(largerThanNumber)), std::out_of_range);
- };
- TEST(StringCalculator, AddsTwoCommaDelimitedNumbers) {
- const StringCalculator c;
- EXPECT_EQ(c.add(",1,2"), 3);
- EXPECT_EQ(c.add("1,2"), 3);
- EXPECT_EQ(c.add(",1,2,"), 3);
- };
- TEST(StringCalculator, AddsTwoNewlineDelimitedNumbers) {
- const StringCalculator c;
- EXPECT_EQ(c.add("\n1\n2"), 3);
- EXPECT_EQ(c.add("\n1\n2\n"), 3);
- EXPECT_EQ(c.add("\n"), 0);
- };
- TEST(StringCalculator, HandlesMultipleDelimiterTypes) {
- const StringCalculator c;
- EXPECT_EQ(c.add("\n1\n2,3,4"), 10);
- EXPECT_EQ(c.add("1,2\n3\n4"), 10);
- EXPECT_EQ(c.add("\n\n1,2,\n3\n,4"), 10);
- };
- TEST(StringCalculator, IgnoresThousandOrHigher) {
- const StringCalculator c;
- EXPECT_EQ(c.add("1000"), 0);
- EXPECT_EQ(c.add("1001"), 0);
- EXPECT_EQ(c.add("100000"), 0);
- EXPECT_EQ(c.add("1,2\n3\n1000"), 6);
- };
- TEST(StringCalculator, ThrowsOnNegatives) {
- const StringCalculator c;
- EXPECT_THROW(c.add("-1"), std::invalid_argument);
- try {
- c.add("-4,-5,7,9\n10,-4"sv);
- } catch (const std::invalid_argument &e) {
- std::string error(e.what());
- EXPECT_EQ(HELP_MSG_ON_NEGATIVE_INPUT + "-4, -5, -4", error);
- }
- };
- TEST(StringCalculator, AcceptsCustomDelimeter) {
- const StringCalculator c;
- EXPECT_EQ(c.add("//p1p5p6"), 12);
- };
- TEST(StringCalculator, AcceptsCustomLongDelimeter) {
- const StringCalculator c;
- EXPECT_EQ(c.add("//po1po5po6"), 12);
- EXPECT_EQ(c.add("//lorem1lorem5lorem6"), 12);
- };
- TEST(join, handlesEmptyRange) {
- Numbers n{};
- const auto string = join(n);
- EXPECT_TRUE(string.empty());
- }
- TEST(join, canJoinTwo) {
- Numbers n{1, 3};
- const auto string = join(n, ", ");
- EXPECT_EQ(string, "1, 3"sv);
- }
- TEST(join, canJoinMany) {
- Numbers n{1, 3, 7, 9, 11};
- const auto string = join(n, ", ");
- EXPECT_EQ(string, "1, 3, 7, 9, 11"sv);
- }
- TEST(join, canJoinStrings) {
- std::vector<std::string> n{"1", "3", "7", "9", "11"};
- const auto string = join(n, ", ");
- ;
- EXPECT_EQ(string, "1, 3, 7, 9, 11");
- }
- TEST(join, canRoundTrip) {
- const auto values = "1, 3, 7, 9, 11"s;
- StringViews s = split(values, DELIMITER);
- const auto output = join(s.begin(), s.end(), ",");
- EXPECT_EQ(output, values);
- }
- TEST(trim, canTrimLeft) {
- string_view input = " 999"sv;
- EXPECT_EQ(trim(input), "999"sv);
- EXPECT_EQ(trimLeft("999 "sv), "999 "sv); // don't touch the right side
- }
- TEST(trim, canTrimEmpty) {
- string_view input = "999"sv;
- EXPECT_EQ(trim(input), "999"sv);
- }
- TEST(trim, canTrimPreTrimmed) {
- EXPECT_EQ(trimRight(""), ""sv);
- string_view input = "999"sv;
- EXPECT_EQ(trim(input), "999"sv);
- EXPECT_EQ(trimRight(input), "999"sv);
- EXPECT_EQ(trimLeft(input), "999"sv);
- }
- TEST(trim, canTrimRight) {
- string_view input = "999 "sv;
- EXPECT_EQ(trim(input), "999"sv);
- EXPECT_EQ(trimRight(" 999"sv), " 999"sv); // don't touch the left side
- }
- TEST(trim, canTrimBoth) {
- string_view input = " 999 "sv;
- EXPECT_EQ(trim(input), "999"sv);
- }
- TEST(trim, canTrimWithSpaceInMiddle) {
- string_view input = " 9 9 9 "sv;
- EXPECT_EQ(trim(input), "9 9 9"sv);
- string_view notrim = "9 9 9"sv;
- EXPECT_EQ(trim(notrim), "9 9 9"sv);
- }
- TEST(split, handlesNoDelimeter) {
- const auto values = split("123456", ",");
- EXPECT_EQ(values.size(), 1);
- }
- TEST(split, canSplitTwo) {
- const auto values = split("1,2", ",");
- EXPECT_EQ(values.size(), 2);
- }
- TEST(split, canSplitMany) {
- const auto values = split("1,2,3,4,5", ",");
- EXPECT_EQ(values.size(), 5);
- EXPECT_TRUE("4" == values[3]);
- }
- TEST(split, ignoresLeadingDelimeter) {
- const auto values = split(",12", ",");
- EXPECT_EQ(values.size(), 1);
- EXPECT_TRUE("12" == values[0]);
- }
- TEST(split, ignoresTrailingDelimeter) {
- const auto values = split("12,", ",");
- EXPECT_EQ(values.size(), 1);
- EXPECT_TRUE("12" == values[0]);
- }
- TEST(split, ignoresEmptyDelimeters) {
- auto values = split("1,,,,,2", ",");
- EXPECT_EQ(values.size(), 2);
- EXPECT_TRUE("2" == values[1]);
- values = split(",,,,,", ",");
- EXPECT_TRUE(values.empty());
- }
- TEST(replace, canHandleMissingNeedle) {
- const auto goal = "em-em-em"sv;
- const auto haystack = "em-em-em"sv;
- const auto needle = "lor"sv; // is not in the haystack!
- const auto replacement = ""sv;
- const auto cleaned = replace(haystack, needle, replacement);
- EXPECT_EQ(cleaned, goal);
- }
- TEST(replace, canReplaceSingleChar) {
- const auto goal = "lorem-ipsum-dolorer"sv;
- const auto haystack = "lorem ipsum dolorer"sv;
- const auto needle = " "sv;
- const auto replacement = "-"sv;
- const auto cleaned = replace(haystack, needle, replacement);
- EXPECT_EQ(cleaned, goal);
- }
- TEST(replace, canReplaceNewline) {
- const auto goal = "em,em,em"sv;
- const auto haystack = "em\nem\nem"sv;
- const auto needle = "\n"sv;
- const auto replacement = ","sv;
- const auto cleaned = replace(haystack, needle, replacement);
- EXPECT_EQ(cleaned, goal);
- }
- TEST(replace, canReplaceSubstrings) {
- const auto goal = "em-em-em"sv;
- const auto haystack = "lorem-lorem-lorem"sv;
- const auto needle = "lor"sv;
- const auto replacement = ""sv;
- const auto cleaned = replace(haystack, needle, replacement);
- EXPECT_EQ(cleaned, goal);
- }
- TEST(replace, canReplaceWithLonger) {
- const auto goal = "ipsum-ipsum-ipsum"sv;
- const auto haystack = "em-em-em"sv;
- const auto needle = "em"sv;
- const auto replacement = "ipsum"sv; // replacement is longer than needle.
- const auto cleaned = replace(haystack, needle, replacement);
- EXPECT_EQ(cleaned, goal);
- }
- TEST(replace, handlesReplacemntContainsNeedle) {
- const auto goal = "lorem-lorem-lorem"sv;
- const auto haystack = "em-em-em"sv;
- const auto needle = "em"sv;
- const auto replacement =
- "lorem"sv; // the replacement contains the needle, "em"
- const auto cleaned = replace(haystack, needle, replacement);
- EXPECT_EQ(cleaned, std::string(goal));
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement