Advertisement
ulfben

StringCalculator

Nov 19th, 2022 (edited)
812
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 12.47 KB | None | 0 0
  1. #include "pch.h"
  2. #include <algorithm>
  3. #include <charconv>
  4. #include <iostream>
  5. #include <iterator>
  6. #include <limits>
  7. #include <numeric>
  8. #include <ranges>
  9. #include <span>
  10. #include <stdexcept>
  11. #include <string>
  12. #include <string_view>
  13.  
  14. using namespace std::string_view_literals;
  15. using namespace std::string_literals;
  16. using std::string_view;
  17. using StringViews = std::vector<string_view>;
  18. using Number = int;
  19. using Numbers = std::vector<Number>;
  20.  
  21. constexpr auto DELIMITER = ","sv;
  22. constexpr auto COMMENT = "//"sv;
  23. const auto HELP_MSG_ON_NEGATIVE_INPUT = "negatives not allowed: "s;
  24. constexpr string_view WHITE_SPACE = " \f\n\r\t\v"sv;
  25. constexpr auto positives = [](auto n) noexcept { return n >= 0; };
  26. constexpr auto negatives = [](auto n) noexcept { return n < 0; };
  27. constexpr auto thousands = [](auto n) noexcept { return n > 999; };
  28.  
  29. [[nodiscard]] Number from_chars(string_view s) {
  30.   Number value;
  31.   auto res = std::from_chars(s.data(), s.data() + s.size(), value);
  32.   // These two exceptions reflect the behavior of std::stoi.
  33.   if (res.ec == std::errc::invalid_argument) {
  34.     throw std::invalid_argument("invalid_argument");
  35.   } else if (res.ec == std::errc::result_out_of_range) {
  36.     throw std::out_of_range("out_of_range");
  37.   }
  38.   return value;
  39. };
  40.  
  41. //TODO: restrict to container types / ranges.
  42. [[nodiscard]] Numbers from_chars(auto v) {
  43.   Numbers results;
  44.   results.reserve(v.size());
  45.   for (auto &sv : v) {
  46.     if (sv.empty()) {
  47.       continue;
  48.     }
  49.     results.push_back(from_chars(sv));
  50.   }
  51.   return results;
  52. }
  53.  
  54. string_view trimLeft(string_view in, string_view delims = WHITE_SPACE) noexcept {
  55.   const auto countFromTheLeft = in.find_first_not_of(delims);
  56.   in.remove_prefix(countFromTheLeft);
  57.   return in;
  58. };
  59. string_view trimRight(string_view in, string_view delims = WHITE_SPACE) noexcept {
  60.   const auto lastValidPos = in.find_last_not_of(delims);
  61.   const auto countFromTheRight = in.size() - lastValidPos - 1;
  62.   in.remove_suffix(countFromTheRight);
  63.   return in;
  64. };
  65.  
  66. string_view trim(string_view in, string_view delims = WHITE_SPACE) noexcept {
  67.   return trimRight(trimLeft(in));
  68. }
  69.  
  70. size_t count(std::string_view str, std::string_view sub) {
  71.     if (sub.empty()) {
  72.         return 0;
  73.     }
  74.     size_t count = 0;
  75.     size_t pos = 0;
  76.     while ((pos = str.find(sub, pos)) != std::string_view::npos) {
  77.         ++count;
  78.         pos += sub.length(); // Move past the current occurrence
  79.     }
  80.     return count;
  81. }
  82.  
  83. /*[[nodiscard]] StringViews split(string_view source, string_view delim) {
  84.   StringViews values;
  85.   std::ranges::split_view outer_view{source, delim};
  86.   for (const auto &split : outer_view) {
  87.     if (split.empty()) {
  88.       continue;
  89.     }
  90.     values.emplace_back(split.begin(), split.end());
  91.   }
  92.   return values;
  93. }*/
  94.  
  95. [[nodiscard]] StringViews split(std::string_view in, std::string_view sep) {
  96.     std::vector<std::string_view> result;    
  97.     if (sep.empty()) {
  98.         result.emplace_back(in);
  99.         return result;
  100.     }
  101.     result.reserve(count(in, sep) + 1);
  102.     size_t startingFrom = 0;
  103.     size_t end;
  104.     while ((end = in.find(sep, startingFrom)) != std::string_view::npos) {
  105.         result.emplace_back(in.substr(startingFrom, end - startingFrom));
  106.         startingFrom = end + sep.size();
  107.     }    
  108.     result.emplace_back(in.substr(startingFrom));
  109.     return result;
  110. }
  111.  
  112. [[nodiscard]] Number sum(auto values) noexcept {
  113.   return std::reduce(std::begin(values), std::end(values));
  114. }
  115.  
  116. [[nodiscard]] bool empty(auto begin, auto end) {
  117.   return std::distance(begin, end) == 0;
  118. }
  119.  
  120. [[nodiscard]] std::string replace(string_view haystack, string_view from,
  121.                                   string_view to) {
  122.   auto clean = std::string(haystack);
  123.   if (from.empty()) {
  124.     return clean;
  125.   }
  126.   size_t pos = 0;
  127.   size_t offset = 0;
  128.   while ((pos = clean.find(from, offset)) != std::string::npos) {
  129.     clean.replace(pos, from.size(), to);
  130.     offset = pos + to.size();
  131.   }
  132.   return clean;
  133. }
  134.  
  135. // overloads to let to_string handle non-numerics
  136. std::string to_string(const std::string &value) noexcept { return value; }
  137. std::string to_string(string_view value) noexcept { return std::string(value); }
  138.  
  139. std::string join(std::input_iterator auto begin, std::input_iterator auto end,
  140.                  const std::string delim = ", "s) {
  141.   if (empty(begin, end)) {
  142.     return {};
  143.   }
  144.   using namespace std;
  145.   // std::accumulate, until we get std::experimental::make_ostream_joiner, or
  146.   // std::format::join
  147.   return std::accumulate(begin + 1, end, to_string(*begin),
  148.                          [&delim](const std::string &a, auto b) {
  149.                            return a + delim + to_string(b);
  150.                          });
  151. }
  152.  
  153. std::string join(auto container, std::string delim = ", "s) {
  154.   return join(std::begin(container), std::end(container), delim);
  155. }
  156.  
  157. void filter(Numbers &v, auto predicate) noexcept {
  158.   std::erase_if(v, predicate);
  159. }
  160.  
  161. bool contains(Numbers &v, auto predicate) {
  162.   return std::ranges::find_if(v, predicate) != v.end();
  163. }
  164.  
  165. void throwBadArguments(Numbers &v) {
  166.   auto subrange =
  167.       std::ranges::stable_partition(v, positives);
  168.   auto invalidValues = join(subrange.begin(), subrange.end());
  169.   throw std::invalid_argument(HELP_MSG_ON_NEGATIVE_INPUT +
  170.                               std::move(invalidValues));
  171. }
  172.  
  173. string_view findDelimeter(string_view &s) noexcept {
  174.   if (s.starts_with(COMMENT) == false) {
  175.     return DELIMITER;
  176.   }
  177.   s.remove_prefix(s.find_first_not_of(COMMENT));
  178.   const auto delimeterLength = s.find_first_of("0123456789");
  179.   return s.substr(0, delimeterLength);
  180. }
  181.  
  182. struct StringCalculator {
  183.   Numbers parseNumbers(string_view s) const {
  184.     if (s.empty()) {
  185.       return {};
  186.     }
  187.     const auto delim = findDelimeter(s);
  188.     const auto clean = replace(s, "\n", delim);
  189.     const auto strings = split(clean, delim);
  190.     return from_chars(strings);
  191.   }
  192.  
  193.   Number add(string_view s) const {
  194.     auto numbers = parseNumbers(s);
  195.     if (contains(numbers, negatives)) {
  196.       throwBadArguments(numbers);
  197.     }
  198.     filter(numbers, thousands);
  199.     return sum(numbers);
  200.   }
  201. };
  202.  
  203. TEST(StringCalculator, ReturnsZeroOnEmpty) {
  204.   const StringCalculator c;
  205.   EXPECT_EQ(c.add(""), 0);
  206. };
  207.  
  208. TEST(StringCalculator, ReturnsSameOnSingleNumber) {
  209.   const StringCalculator c;
  210.   EXPECT_EQ(c.add("1"), 1);
  211.   EXPECT_EQ(c.add("9"), 9);
  212.   EXPECT_EQ(c.add("878"), 878);
  213. };
  214.  
  215. TEST(StringCalculator, ThrowsOnBadInput) {
  216.   const StringCalculator c;
  217.   EXPECT_THROW(c.add("p"), std::invalid_argument);
  218.   constexpr std::size_t largerThanNumber =
  219.       std::numeric_limits<Number>::max() + std::size_t(1);
  220.   EXPECT_THROW(c.add(std::to_string(largerThanNumber)), std::out_of_range);
  221. };
  222.  
  223. TEST(StringCalculator, AddsTwoCommaDelimitedNumbers) {
  224.   const StringCalculator c;
  225.   EXPECT_EQ(c.add(",1,2"), 3);
  226.   EXPECT_EQ(c.add("1,2"), 3);
  227.   EXPECT_EQ(c.add(",1,2,"), 3);
  228. };
  229.  
  230. TEST(StringCalculator, AddsTwoNewlineDelimitedNumbers) {
  231.   const StringCalculator c;
  232.   EXPECT_EQ(c.add("\n1\n2"), 3);
  233.   EXPECT_EQ(c.add("\n1\n2\n"), 3);
  234.   EXPECT_EQ(c.add("\n"), 0);
  235. };
  236.  
  237. TEST(StringCalculator, HandlesMultipleDelimiterTypes) {
  238.   const StringCalculator c;
  239.   EXPECT_EQ(c.add("\n1\n2,3,4"), 10);
  240.   EXPECT_EQ(c.add("1,2\n3\n4"), 10);
  241.   EXPECT_EQ(c.add("\n\n1,2,\n3\n,4"), 10);
  242. };
  243.  
  244. TEST(StringCalculator, IgnoresThousandOrHigher) {
  245.   const StringCalculator c;
  246.   EXPECT_EQ(c.add("1000"), 0);
  247.   EXPECT_EQ(c.add("1001"), 0);
  248.   EXPECT_EQ(c.add("100000"), 0);
  249.   EXPECT_EQ(c.add("1,2\n3\n1000"), 6);
  250. };
  251.  
  252. TEST(StringCalculator, ThrowsOnNegatives) {
  253.   const StringCalculator c;
  254.   EXPECT_THROW(c.add("-1"), std::invalid_argument);
  255.   try {
  256.     c.add("-4,-5,7,9\n10,-4"sv);
  257.   } catch (const std::invalid_argument &e) {
  258.     std::string error(e.what());
  259.     EXPECT_EQ(HELP_MSG_ON_NEGATIVE_INPUT + "-4, -5, -4", error);
  260.   }
  261. };
  262.  
  263. TEST(StringCalculator, AcceptsCustomDelimeter) {
  264.   const StringCalculator c;
  265.   EXPECT_EQ(c.add("//p1p5p6"), 12);
  266. };
  267.  
  268. TEST(StringCalculator, AcceptsCustomLongDelimeter) {
  269.   const StringCalculator c;
  270.   EXPECT_EQ(c.add("//po1po5po6"), 12);
  271.   EXPECT_EQ(c.add("//lorem1lorem5lorem6"), 12);
  272. };
  273. TEST(join, handlesEmptyRange) {
  274.   Numbers n{};
  275.   const auto string = join(n);
  276.   EXPECT_TRUE(string.empty());
  277. }
  278. TEST(join, canJoinTwo) {
  279.   Numbers n{1, 3};
  280.   const auto string = join(n, ", ");
  281.   EXPECT_EQ(string, "1, 3"sv);
  282. }
  283. TEST(join, canJoinMany) {
  284.   Numbers n{1, 3, 7, 9, 11};
  285.   const auto string = join(n, ", ");
  286.   EXPECT_EQ(string, "1, 3, 7, 9, 11"sv);
  287. }
  288. TEST(join, canJoinStrings) {
  289.   std::vector<std::string> n{"1", "3", "7", "9", "11"};
  290.   const auto string = join(n, ", ");
  291.   ;
  292.   EXPECT_EQ(string, "1, 3, 7, 9, 11");
  293. }
  294. TEST(join, canRoundTrip) {
  295.   const auto values = "1, 3, 7, 9, 11"s;
  296.   StringViews s = split(values, DELIMITER);
  297.   const auto output = join(s.begin(), s.end(), ",");
  298.   EXPECT_EQ(output, values);
  299. }
  300.  
  301. TEST(trim, canTrimLeft) {
  302.   string_view input = "     999"sv;
  303.   EXPECT_EQ(trim(input), "999"sv);
  304.   EXPECT_EQ(trimLeft("999     "sv), "999     "sv); // don't touch the right side
  305. }
  306. TEST(trim, canTrimEmpty) {
  307.   string_view input = "999"sv;
  308.   EXPECT_EQ(trim(input), "999"sv);
  309. }
  310.  
  311. TEST(trim, canTrimPreTrimmed) {
  312.   EXPECT_EQ(trimRight(""), ""sv);
  313.  
  314.   string_view input = "999"sv;
  315.   EXPECT_EQ(trim(input), "999"sv);
  316.   EXPECT_EQ(trimRight(input), "999"sv);
  317.   EXPECT_EQ(trimLeft(input), "999"sv);
  318. }
  319. TEST(trim, canTrimRight) {
  320.   string_view input = "999     "sv;
  321.   EXPECT_EQ(trim(input), "999"sv);
  322.   EXPECT_EQ(trimRight("    999"sv), "    999"sv); // don't touch the left side
  323. }
  324.  
  325. TEST(trim, canTrimBoth) {
  326.   string_view input = "      999     "sv;
  327.   EXPECT_EQ(trim(input), "999"sv);
  328. }
  329.  
  330. TEST(trim, canTrimWithSpaceInMiddle) {
  331.   string_view input = "      9 9 9     "sv;
  332.   EXPECT_EQ(trim(input), "9 9 9"sv);
  333.  
  334.   string_view notrim = "9 9 9"sv;
  335.   EXPECT_EQ(trim(notrim), "9 9 9"sv);
  336. }
  337.  
  338. TEST(split, handlesNoDelimeter) {
  339.   const auto values = split("123456", ",");
  340.   EXPECT_EQ(values.size(), 1);
  341. }
  342. TEST(split, canSplitTwo) {
  343.   const auto values = split("1,2", ",");
  344.   EXPECT_EQ(values.size(), 2);
  345. }
  346. TEST(split, canSplitMany) {
  347.   const auto values = split("1,2,3,4,5", ",");
  348.   EXPECT_EQ(values.size(), 5);
  349.   EXPECT_TRUE("4" == values[3]);
  350. }
  351. TEST(split, ignoresLeadingDelimeter) {
  352.   const auto values = split(",12", ",");
  353.   EXPECT_EQ(values.size(), 1);
  354.   EXPECT_TRUE("12" == values[0]);
  355. }
  356. TEST(split, ignoresTrailingDelimeter) {
  357.   const auto values = split("12,", ",");
  358.   EXPECT_EQ(values.size(), 1);
  359.   EXPECT_TRUE("12" == values[0]);
  360. }
  361. TEST(split, ignoresEmptyDelimeters) {
  362.   auto values = split("1,,,,,2", ",");
  363.   EXPECT_EQ(values.size(), 2);
  364.   EXPECT_TRUE("2" == values[1]);
  365.  
  366.   values = split(",,,,,", ",");
  367.   EXPECT_TRUE(values.empty());
  368. }
  369.  
  370. TEST(replace, canHandleMissingNeedle) {
  371.   const auto goal = "em-em-em"sv;
  372.   const auto haystack = "em-em-em"sv;
  373.   const auto needle = "lor"sv; // is not in the haystack!
  374.   const auto replacement = ""sv;
  375.   const auto cleaned = replace(haystack, needle, replacement);
  376.   EXPECT_EQ(cleaned, goal);
  377. }
  378. TEST(replace, canReplaceSingleChar) {
  379.   const auto goal = "lorem-ipsum-dolorer"sv;
  380.   const auto haystack = "lorem ipsum dolorer"sv;
  381.   const auto needle = " "sv;
  382.   const auto replacement = "-"sv;
  383.   const auto cleaned = replace(haystack, needle, replacement);
  384.   EXPECT_EQ(cleaned, goal);
  385. }
  386.  
  387. TEST(replace, canReplaceNewline) {
  388.   const auto goal = "em,em,em"sv;
  389.   const auto haystack = "em\nem\nem"sv;
  390.   const auto needle = "\n"sv;
  391.   const auto replacement = ","sv;
  392.   const auto cleaned = replace(haystack, needle, replacement);
  393.   EXPECT_EQ(cleaned, goal);
  394. }
  395. TEST(replace, canReplaceSubstrings) {
  396.   const auto goal = "em-em-em"sv;
  397.   const auto haystack = "lorem-lorem-lorem"sv;
  398.   const auto needle = "lor"sv;
  399.   const auto replacement = ""sv;
  400.   const auto cleaned = replace(haystack, needle, replacement);
  401.   EXPECT_EQ(cleaned, goal);
  402. }
  403.  
  404. TEST(replace, canReplaceWithLonger) {
  405.   const auto goal = "ipsum-ipsum-ipsum"sv;
  406.   const auto haystack = "em-em-em"sv;
  407.   const auto needle = "em"sv;
  408.   const auto replacement = "ipsum"sv; // replacement is longer than needle.
  409.   const auto cleaned = replace(haystack, needle, replacement);
  410.   EXPECT_EQ(cleaned, goal);
  411. }
  412.  
  413. TEST(replace, handlesReplacemntContainsNeedle) {
  414.   const auto goal = "lorem-lorem-lorem"sv;
  415.   const auto haystack = "em-em-em"sv;
  416.   const auto needle = "em"sv;
  417.   const auto replacement =
  418.       "lorem"sv; // the replacement contains the needle, "em"
  419.   const auto cleaned = replace(haystack, needle, replacement);
  420.   EXPECT_EQ(cleaned, std::string(goal));
  421. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement