diff --git a/CMakeLists.txt b/CMakeLists.txt index 32b412f..ea5fbfb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,6 +11,39 @@ project( find_package(GTest CONFIG REQUIRED) +file(GLOB LIB_SRCS "common/**.cpp") + +add_library( + common STATIC + ${LIB_SRCS} +) + +target_include_directories( + common PUBLIC + common/ +) + +file(GLOB LIB_TESTS "common-tests/src/**.cpp") + +foreach(FILE IN LISTS LIB_TESTS) + cmake_path(GET FILE STEM TEST) + + add_executable("common-tests-${TEST}" "${FILE}") + + target_link_libraries( + "common-tests-${TEST}" PRIVATE + common + GTest::gtest_main + ) + + set_target_properties( + "common-tests-${TEST}" PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/common-tests/bin" + RUNTIME_OUTPUT_NAME "test-${TEST}" + SUFFIX "" + ) +endforeach() + file(GLOB SOURCES "problem-*/") foreach(FILE IN LISTS SOURCES) @@ -37,24 +70,24 @@ foreach(FILE IN LISTS SOURCES) target_link_libraries( "${PROBLEM}" PRIVATE + common ) target_link_libraries( "test-${PROBLEM}" PRIVATE + common GTest::gtest_main ) target_include_directories( "${PROBLEM}" PRIVATE "${FILE}/src" - "${CMAKE_CURRENT_SOURCE_DIR}/common" ) target_include_directories( "test-${PROBLEM}" PRIVATE "${FILE}/src" - "${CMAKE_CURRENT_SOURCE_DIR}/common" ) set_target_properties( diff --git a/common-tests/src/bigint.cpp b/common-tests/src/bigint.cpp new file mode 100644 index 0000000..12d9d3f --- /dev/null +++ b/common-tests/src/bigint.cpp @@ -0,0 +1,229 @@ +/* + * Project Euler Solutions - Test File + * ------------------------------------ + * Problem : Testing + * URL : https://projecteuler.net/problem=test + * + * Author : erick-alcachofa + * Created : Friday, September 05 2025 + * + * Notes: + * - + * + * License : GNU Affero General Public License v3.0 (AGPLv3) + * https://www.gnu.org/licenses/agpl-3.0.html + */ + +#include + +#include "bigint.hpp" + +// Helper macro for creating BigInt from string, fails test on error +#define BI(str) BigInt::fromString(str).value() + +TEST(FromString, HandlesPositiveNumbers) { + EXPECT_EQ(BI("12345").toString(), "12345"); + EXPECT_EQ(BI("0").toString(), "0"); + EXPECT_EQ(BI("99").toString(), "99"); + EXPECT_EQ(BI("100").toString(), "100"); +} + +TEST(FromString, HandlesNegativeNumbers) { + EXPECT_EQ(BI("-12345").toString(), "-12345"); + EXPECT_EQ(BI("-99").toString(), "-99"); +} + +TEST(FromString, HandlesZero) { + EXPECT_EQ(BI("0").toString(), "0"); + EXPECT_EQ(BI("-0").toString(), "0"); // Canonical zero +} + +TEST(FromString, HandlesWhitespaceAndSigns) { + EXPECT_EQ(BI(" 123 ").toString(), "123"); + EXPECT_EQ(BI("+456").toString(), "456"); + EXPECT_EQ(BI(" -789 ").toString(), "-789"); +} + +TEST(FromString, HandlesInvalidInput) { + EXPECT_FALSE(BigInt::fromString("").has_value()); + EXPECT_FALSE(BigInt::fromString("-").has_value()); + EXPECT_FALSE(BigInt::fromString("12a34").has_value()); + EXPECT_FALSE(BigInt::fromString("abc").has_value()); +} + +TEST(Addition, PositivePlusPositive) { + EXPECT_EQ((BI("123") + BI("456")).toString(), "579"); + EXPECT_EQ((BI("99") + BI("1")).toString(), "100"); // With carry + EXPECT_EQ((BI("9999") + BI("1")).toString(), "10000"); +} + +TEST(Addition, NegativePlusNegative) { + EXPECT_EQ((BI("-123") + BI("-456")).toString(), "-579"); + EXPECT_EQ((BI("-99") + BI("-1")).toString(), "-100"); +} + +TEST(Addition, MixedSigns) { + EXPECT_EQ((BI("500") + BI("-200")).toString(), "300"); + EXPECT_EQ((BI("200") + BI("-500")).toString(), "-300"); + EXPECT_EQ((BI("-500") + BI("200")).toString(), "-300"); + EXPECT_EQ((BI("-200") + BI("500")).toString(), "300"); + EXPECT_EQ((BI("200") + BI("-200")).toString(), "0"); +} + +TEST(Subtraction, PositiveMinusPositive) { + EXPECT_EQ((BI("500") - BI("200")).toString(), "300"); + EXPECT_EQ((BI("200") - BI("500")).toString(), "-300"); + EXPECT_EQ((BI("100") - BI("1")).toString(), "99"); // With borrow +} + +TEST(Subtraction, MixedSigns) { + EXPECT_EQ((BI("200") - BI("-300")).toString(), "500"); + EXPECT_EQ((BI("-200") - BI("300")).toString(), "-500"); + EXPECT_EQ((BI("-500") - BI("-200")).toString(), "-300"); +} + +TEST(Subtraction, ResultingInZero) { + EXPECT_EQ((BI("200") - BI("200")).toString(), "0"); + EXPECT_EQ((BI("-200") - BI("-200")).toString(), "0"); +} + +TEST(Multiplication, BasicCases) { + EXPECT_EQ((BI("12") * BI("10")).toString(), "120"); + EXPECT_EQ((BI("123") * BI("456")).toString(), "56088"); + EXPECT_EQ((BI("99") * BI("99")).toString(), "9801"); +} + +TEST(Multiplication, WithZero) { + EXPECT_EQ((BI("123") * BI("0")).toString(), "0"); + EXPECT_EQ((BI("0") * BI("123")).toString(), "0"); + EXPECT_EQ((BI("-123") * BI("0")).toString(), "0"); +} + +TEST(Multiplication, WithNegativeNumbers) { + EXPECT_EQ((BI("-12") * BI("10")).toString(), "-120"); + EXPECT_EQ((BI("12") * BI("-10")).toString(), "-120"); + EXPECT_EQ((BI("-12") * BI("-10")).toString(), "120"); +} + +TEST(DivisionAndRemainder, DivRemPositive) { + auto result = BigInt::divmod(BI("12345"), BI("54")); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->quotient.toString(), "228"); + EXPECT_EQ(result->remainder.toString(), "33"); + + result = BigInt::divmod(BI("100"), BI("10")); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->quotient.toString(), "10"); + EXPECT_EQ(result->remainder.toString(), "0"); +} + +TEST(DivisionAndRemainder, DivRemNegative) { + // -12345 / 54 -> Q = -228, R = -33 + auto result = BigInt::divmod(BI("-12345"), BI("54")); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->quotient.toString(), "-228"); + EXPECT_EQ(result->remainder.toString(), "-33"); + + // 12345 / -54 -> Q = -228, R = 33 + result = BigInt::divmod(BI("12345"), BI("-54")); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->quotient.toString(), "-228"); + EXPECT_EQ(result->remainder.toString(), "33"); + + // -12345 / -54 -> Q = 228, R = -33 + result = BigInt::divmod(BI("-12345"), BI("-54")); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->quotient.toString(), "228"); + EXPECT_EQ(result->remainder.toString(), "-33"); +} + +TEST(DivisionAndRemainder, QuotientOnly) { + auto q = BigInt::quotient(BI("56088"), BI("123")); + ASSERT_TRUE(q.has_value()); + EXPECT_EQ(q->toString(), "456"); + + q = BigInt::quotient(BI("-100"), BI("9")); + ASSERT_TRUE(q.has_value()); + EXPECT_EQ(q->toString(), "-11"); +} + +TEST(DivisionAndRemainder, RemainderOnly) { + auto r_pos = BigInt::remainder(BI("100"), BI("9")); + ASSERT_TRUE(r_pos.has_value()); + EXPECT_EQ(r_pos->toString(), "1"); + + auto r_neg = BigInt::remainder(BI("-100"), BI("9")); + ASSERT_TRUE(r_neg.has_value()); + EXPECT_EQ(r_neg->toString(), "-1"); +} + +TEST(DivisionAndRemainder, DivisionByZero) { + EXPECT_FALSE(BigInt::divmod(BI("100"), BI("0")).has_value()); + EXPECT_FALSE(BigInt::quotient(BI("100"), BI("0")).has_value()); + EXPECT_FALSE(BigInt::remainder(BI("100"), BI("0")).has_value()); +} + +TEST(DivisionAndRemainder, DividendSmallerThanDivisor) { + auto result = BigInt::divmod(BI("10"), BI("100")); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->quotient.toString(), "0"); + EXPECT_EQ(result->remainder.toString(), "10"); + + result = BigInt::divmod(BI("-10"), BI("100")); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->quotient.toString(), "0"); + EXPECT_EQ(result->remainder.toString(), "-10"); +} + +TEST(Comparison, AllOperators) { + EXPECT_TRUE(BI("100") > BI("99")); + EXPECT_TRUE(BI("99") < BI("100")); + EXPECT_TRUE(BI("-99") > BI("-100")); + EXPECT_TRUE(BI("-100") < BI("-99")); + EXPECT_TRUE(BI("100") >= BI("100")); + EXPECT_TRUE(BI("100") <= BI("100")); + EXPECT_TRUE(BI("100") == BI("100")); + EXPECT_TRUE(BI("100") != BI("101")); + EXPECT_TRUE(BI("100") > BI("-100")); + EXPECT_TRUE(BI("-100") < BI("100")); +} + +TEST(LargeValues, HandlesValuesExceedingULLONG_MAX) { + // ULLONG_MAX is 18,446,744,073,709,551,615. These numbers are larger. + const auto big_num1 = BI("20000000000000000000"); // 2 * 10^19 + const auto big_num2 = BI("10000000000000000000"); // 1 * 10^19 + const auto neg_big_num1 = BI("-20000000000000000000"); + + // Addition + EXPECT_EQ((big_num1 + big_num2).toString(), "30000000000000000000"); + + // Subtraction + EXPECT_EQ((big_num1 - big_num2).toString(), "10000000000000000000"); + + // Multiplication + EXPECT_EQ((big_num1 * big_num2).toString(), + "200000000000000000000000000000000000000"); + + // Division and Remainder + auto result = BigInt::divmod(big_num1, big_num2); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->quotient.toString(), "2"); + EXPECT_EQ(result->remainder.toString(), "0"); + + // Comparisons + EXPECT_TRUE(big_num1 > big_num2); + EXPECT_TRUE(big_num2 < big_num1); + EXPECT_TRUE(big_num1 > neg_big_num1); + EXPECT_TRUE(neg_big_num1 < big_num2); + + EXPECT_EQ((BI("7284792756374829238474859") * BI("28347299847928374918237")) + .toString(), + "206504204594973924026042468998840507198705103583"); + + result = BigInt::divmod( + BI("206504204594973924026042468998840507198705103583"), BI("38294791")); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->quotient.toString(), + "5392488095704032541293735458664352214344"); + EXPECT_EQ(result->remainder.toString(), "14421479"); +} diff --git a/common/bigint.cpp b/common/bigint.cpp new file mode 100644 index 0000000..3e6c55e --- /dev/null +++ b/common/bigint.cpp @@ -0,0 +1,420 @@ +#include "bigint.hpp" + +#include +#include +#include +#include +#include + +std::expected +BigInt::fromString(std::string_view value) { + BigInt result; + + value = trim(value, isSpace); + + if (value.empty()) { + return std::unexpected{"Invalid value: input string is empty."}; + } + + if (value.front() == '-') { + result.isNegative = true; + value.remove_prefix(1); + } else if (value.front() == '+') { + value.remove_prefix(1); + } + + if (value.empty()) { + return std::unexpected{"Invalid value: string contains only a sign."}; + } + + if (rg::any_of(value, [](u8 ch) { return !std::isdigit(ch); })) { + return std::unexpected{"Invalid value: contains non-digit characters."}; + } + + result.digits.reserve(value.size() / 2 + 1); + + auto it = value.begin(); + + if ((value.size() % 2) == 1) { + result.digits.push_back(u8(*it++ - '0')); + } + + while (it != value.end()) { + u8 cval = u8((*it++ - '0') * 10); + cval += u8((*it++ - '0')); + result.digits.push_back(cval); + } + + rg::reverse(result.digits); + + result.trimLeadingZeroes(); + + if (result.isZero()) { + result.isNegative = false; + } + + return result; +} + +std::string BigInt::toString() const { + if (this->isZero()) { + return "0"; + } + + std::stringstream ss; + + if (isNegative) { + ss << "-"; + } + + ss << i32(digits.back()); + + for (const auto &d : this->digits | vw::reverse | vw::drop(1)) { + ss << std::format("{:02}", i32(d)); + } + + return ss.str(); +} + +void BigInt::trimLeadingZeroes() noexcept { + while (!digits.empty() && digits.back() == 0) { + digits.pop_back(); + } +} + +BigInt BigInt::addMagnitudes(const BigInt &lhs, const BigInt &rhs) { + BigInt result; + u16 carry = 0; + + const auto &longer = [&] -> const BigInt & { + if (lhs.size() > rhs.size()) { + return lhs; + } + return rhs; + }(); + + const auto &shorter = [&] -> const BigInt & { + if (lhs.size() <= rhs.size()) { + return lhs; + } + return rhs; + }(); + + result.digits.reserve(longer.size() + 1); + + for (u64 i = 0; i < shorter.size(); ++i) { + u16 sum = shorter.digits[i] + longer.digits[i] + carry; + + carry = sum / Base; + sum %= Base; + + result.digits.push_back(sum); + } + + for (u64 i = shorter.size(); i < longer.size(); ++i) { + u16 sum = longer.digits[i] + carry; + + carry = sum / Base; + sum %= Base; + + result.digits.push_back(sum); + } + + if (carry != 0) { + result.digits.push_back(carry); + } + + return result; +} + +BigInt BigInt::subMagnitudes(const BigInt &lhs, const BigInt &rhs) { + BigInt result; + i16 borrow = 0; + + result.digits.reserve(lhs.size() + 1); + + for (u64 i = 0; i < rhs.size(); ++i) { + i16 diff = lhs.digits[i] - rhs.digits[i] - borrow; + + if (diff < 0) { + diff += Base; + borrow = 1; + } else { + borrow = 0; + } + + result.digits.push_back(u8(diff)); + } + + for (u64 i = rhs.size(); i < lhs.size(); ++i) { + i16 diff = lhs.digits[i] - borrow; + + if (diff < 0) { + diff += Base; + borrow = 1; + } else { + borrow = 0; + } + + result.digits.push_back(u8(diff)); + } + + result.trimLeadingZeroes(); + + return result; +} + +BigInt BigInt::multiplyMagnitudes(const BigInt &lhs, const BigInt &rhs) { + if (lhs.isZero() || rhs.isZero()) { + return BigInt{}; + } + + std::vector accumulator(lhs.size() + rhs.size() + 1, 0); + + const auto &longer = [&] -> const BigInt & { + if (lhs.size() > rhs.size()) { + return lhs; + } + return rhs; + }(); + + const auto &shorter = [&] -> const BigInt & { + if (lhs.size() <= rhs.size()) { + return lhs; + } + return rhs; + }(); + + for (const auto &[bottomIdx, bottom] : vw::enumerate(shorter.digits)) { + u32 carry = 0; + + for (const auto &[topIdx, top] : vw::enumerate(longer.digits)) { + u32 prod = bottom * top + carry; + + carry = prod / Base; + prod %= Base; + + accumulator[bottomIdx + topIdx] += prod; + } + + if (carry != 0) { + accumulator[longer.size() + bottomIdx] += carry; + } + } + + BigInt result; + u32 carry = 0; + + result.digits.reserve(accumulator.size() + 2); + + for (auto &prod : accumulator) { + prod += carry; + + carry = prod / Base; + prod %= Base; + + result.digits.push_back(prod); + } + + while (carry != 0) { + result.digits.push_back(carry % Base); + carry /= Base; + } + + result.trimLeadingZeroes(); + + return result; +} + +std::strong_ordering BigInt::compareMagnitudes(const BigInt &lhs, + const BigInt &rhs) noexcept { + if (lhs.size() > rhs.size()) + return std::strong_ordering::greater; + + if (lhs.size() < rhs.size()) + return std::strong_ordering::less; + + for (const auto &[left, right] : + vw::zip(lhs.digits, rhs.digits) | vw::reverse) { + if (left > right) + return std::strong_ordering::greater; + + if (left < right) + return std::strong_ordering::less; + } + + return std::strong_ordering::equal; +} + +BigInt operator+(const BigInt &lhs, const BigInt &rhs) { + if (lhs.isNegative == rhs.isNegative) { + BigInt result = BigInt::addMagnitudes(lhs, rhs); + result.isNegative = lhs.isNegative; + return result; + } + + auto comparison = BigInt::compareMagnitudes(lhs, rhs); + + if (comparison == std::strong_ordering::greater) { + BigInt result = BigInt::subMagnitudes(lhs, rhs); + result.isNegative = lhs.isNegative; + return result; + } + + if (comparison == std::strong_ordering::less) { + BigInt result = BigInt::subMagnitudes(rhs, lhs); + result.isNegative = rhs.isNegative; + return result; + } + + return BigInt{}; +} + +BigInt operator-(const BigInt &lhs, const BigInt &rhs) { + BigInt negated_rhs = rhs; + + if (!negated_rhs.isZero()) { + negated_rhs.isNegative = !rhs.isNegative; + } + + return lhs + negated_rhs; +} + +BigInt operator*(const BigInt &lhs, const BigInt &rhs) { + BigInt result = BigInt::multiplyMagnitudes(lhs, rhs); + + result.isNegative = lhs.isNegative ^ rhs.isNegative; + + if (result.isZero()) { + result.isNegative = false; + } + + return result; +} + +std::expected +BigInt::divmod(const BigInt &lhs, const BigInt &rhs) { + DivisionResult result{}; + + if (rhs.isZero()) { + return std::unexpected{"Division by zero"}; + } + + if (lhs.isZero()) { + return result; + } + + if (BigInt::compareMagnitudes(lhs, rhs) == std::strong_ordering::less) { + result.remainder = lhs; + return result; + } + + auto "ient = result.quotient; + auto &remainder = result.remainder; + + remainder.digits.reserve(rhs.size()); + + for (const auto &digit : vw::reverse(lhs.digits)) { + remainder.digits.insert(remainder.digits.begin(), digit); + remainder.trimLeadingZeroes(); + + if (BigInt::compareMagnitudes(remainder, rhs) != + std::strong_ordering::less) { + i16 quotDInt = 0; + BigInt quotD{}; + + for (i16 iter = Base - 1; iter >= 0; --iter) { + auto bigIter = BigInt::fromInteger(iter); + + if (!bigIter) { + return std::unexpected{bigIter.error()}; + } + + auto mult = BigInt::multiplyMagnitudes(rhs, bigIter.value()); + + if (BigInt::compareMagnitudes(mult, remainder) != + std::strong_ordering::greater) { + quotDInt = iter; + quotD = std::move(mult); + break; + } + } + + remainder = BigInt::subMagnitudes(remainder, quotD); + quotient.digits.push_back(quotDInt); + } + } + + rg::reverse(quotient.digits); + quotient.trimLeadingZeroes(); + + if (!quotient.isZero()) { + quotient.isNegative = lhs.isNegative ^ rhs.isNegative; + } + + if (!remainder.isZero()) { + remainder.isNegative = lhs.isNegative; + } + + return result; +} + +std::expected BigInt::quotient(const BigInt &lhs, + const BigInt &rhs) { + auto result = BigInt::divmod(lhs, rhs); + + if (result) { + return result->quotient; + } + + return std::unexpected(result.error()); +} + +std::expected BigInt::remainder(const BigInt &lhs, + const BigInt &rhs) { + auto result = BigInt::divmod(lhs, rhs); + + if (result) { + return result->remainder; + } + + return std::unexpected(result.error()); +} + +std::strong_ordering operator<=>(const BigInt &lhs, const BigInt &rhs) { + if (lhs.isZero() && rhs.isZero()) { + return std::strong_ordering::equal; + } + + if (lhs.isNegative && !rhs.isNegative) { + return std::strong_ordering::less; + } + + if (!lhs.isNegative && rhs.isNegative) { + return std::strong_ordering::greater; + } + + auto magnitude_comparison = BigInt::compareMagnitudes(lhs, rhs); + + // If both are negative, the ordering is the reverse of their magnitude. + // e.g., magnitude of -100 > magnitude of -90, but -100 < -90. + if (lhs.isNegative) { + if (magnitude_comparison == std::strong_ordering::less) + return std::strong_ordering::greater; + + if (magnitude_comparison == std::strong_ordering::greater) + return std::strong_ordering::less; + + return std::strong_ordering::equal; + } + + return magnitude_comparison; +} + +bool operator==(const BigInt &lhs, const BigInt &rhs) { + return (lhs <=> rhs) == std::strong_ordering::equal; +} + +bool operator!=(const BigInt &lhs, const BigInt &rhs) { + return !(lhs == rhs); +} diff --git a/common/bigint.hpp b/common/bigint.hpp new file mode 100644 index 0000000..bb6c57d --- /dev/null +++ b/common/bigint.hpp @@ -0,0 +1,86 @@ +#pragma once + +#include +#include +#include +#include + +#include +#include + +struct BigInt { + static constexpr u32 Base = 100; + + bool isNegative = false; + std::vector digits; + + struct DivisionResult; + + friend BigInt operator+(const BigInt &, const BigInt &); + friend BigInt operator-(const BigInt &, const BigInt &); + friend BigInt operator*(const BigInt &, const BigInt &); + friend std::strong_ordering operator<=>(const BigInt &lhs, const BigInt &rhs); + + static std::expected + fromString(std::string_view value); + + static std::expected + fromInteger(std::integral auto value) { + auto str = std::to_string(value); + return BigInt::fromString(str); + } + + std::string toString() const; + + inline bool isZero() const noexcept { + return digits.empty(); + } + + BigInt() = default; + BigInt(BigInt &&) = default; + BigInt(const BigInt &) = default; + BigInt &operator=(BigInt &&) = default; + BigInt &operator=(const BigInt &) = default; + ~BigInt() = default; + + static std::expected divmod(const BigInt &lhs, + const BigInt &rhs); + + static std::expected quotient(const BigInt &lhs, + const BigInt &rhs); + + static std::expected remainder(const BigInt &lhs, + const BigInt &rhs); + +private: + void trimLeadingZeroes() noexcept; + + inline u64 size() const noexcept { + return this->digits.size(); + } + + static BigInt addMagnitudes(const BigInt &lhs, const BigInt &rhs); + + static BigInt subMagnitudes(const BigInt &lhs, const BigInt &rhs); + + static BigInt multiplyMagnitudes(const BigInt &lhs, const BigInt &rhs); + + static std::strong_ordering compareMagnitudes(const BigInt &lhs, + const BigInt &rhs) noexcept; +}; + +struct BigInt::DivisionResult { + BigInt quotient; + BigInt remainder; +}; + +BigInt operator+(const BigInt &lhs, const BigInt &rhs); + +BigInt operator-(const BigInt &lhs, const BigInt &rhs); + +BigInt operator*(const BigInt &lhs, const BigInt &rhs); + +std::strong_ordering operator<=>(const BigInt &lhs, const BigInt &rhs); + +bool operator==(const BigInt &lhs, const BigInt &rhs); +bool operator!=(const BigInt &lhs, const BigInt &rhs); diff --git a/common/strings.hpp b/common/strings.hpp new file mode 100644 index 0000000..272fc29 --- /dev/null +++ b/common/strings.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include +#include +#include + +static inline bool isSpace(char ch) { + return std::isspace(ch); +} + +static std::string_view ltrim(std::string_view str, + std::function filter = isSpace) { + auto it = str.begin(); + + while (it != str.end()) { + if (!filter(*it)) { + break; + } + + ++it; + } + + return {it, str.end()}; +} + +static std::string_view rtrim(std::string_view str, + std::function filter = isSpace) { + auto it = str.rbegin(); + + while (it != str.rend()) { + if (!filter(*it)) { + break; + } + + ++it; + } + + return {str.begin(), it.base()}; +} + +static std::string_view trim(std::string_view str, + std::function filter = isSpace) { + return ltrim(rtrim(str, filter), filter); +} diff --git a/common/types.hpp b/common/types.hpp new file mode 100644 index 0000000..a1aa224 --- /dev/null +++ b/common/types.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include +#include + +using u8 = uint8_t; +using u16 = uint16_t; +using u32 = uint32_t; +using u64 = uint64_t; + +using i8 = int8_t; +using i16 = int16_t; +using i32 = int32_t; +using i64 = int64_t; + +using f32 = float; +using f64 = double; + +namespace rg = std::ranges; +namespace vw = std::views;