From 61810a46a8a73fd7de0312841553c30009150ed4 Mon Sep 17 00:00:00 2001 From: mathusanm6 Date: Fri, 29 Aug 2025 17:16:20 +0200 Subject: [PATCH 1/4] fix: a lot --- .clang-format | 106 ++++++++++++++++ .clang-tidy | 63 ++++++++++ .github/workflows/linter-cpp.yml | 49 ++++++++ .github/workflows/linter-py.yml | 43 +++++++ .github/workflows/postsubmit-cpp.yml | 40 ++++++ .github/workflows/postsubmit-py.yml | 35 ++++++ .../{cpp-package.yml => presubmit-cpp.yml} | 4 - ...package-conda.yml => presubmit-python.yml} | 4 - README.md | 55 ++++++-- problems/two_sum/two_sum.cc | 22 ++-- problems/two_sum/two_sum_test.cc | 119 ++++++++---------- problems/valid_palindrome/valid_palindrome.cc | 49 ++++---- problems/valid_palindrome/valid_palindrome.h | 2 +- .../valid_palindrome/valid_palindrome_test.cc | 106 ++++++---------- 14 files changed, 506 insertions(+), 191 deletions(-) create mode 100644 .clang-format create mode 100644 .clang-tidy create mode 100644 .github/workflows/linter-cpp.yml create mode 100644 .github/workflows/linter-py.yml create mode 100644 .github/workflows/postsubmit-cpp.yml create mode 100644 .github/workflows/postsubmit-py.yml rename .github/workflows/{cpp-package.yml => presubmit-cpp.yml} (97%) rename .github/workflows/{python-package-conda.yml => presubmit-python.yml} (97%) diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..30c1fa5 --- /dev/null +++ b/.clang-format @@ -0,0 +1,106 @@ +--- +# Configuration for clang-format +# Based on Google C++ Style Guide with some modifications + +BasedOnStyle: Google + +# Indentation +IndentWidth: 4 +TabWidth: 4 +UseTab: Never +ContinuationIndentWidth: 4 + +# Alignment +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Left +AlignOperands: true +AlignTrailingComments: true + +# Allow putting parameters of a function declaration onto the next line +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: All +AllowShortIfStatementsOnASingleLine: true +AllowShortLoopsOnASingleLine: true + +# Breaking +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: true +AlwaysBreakTemplateDeclarations: Yes +BinPackArguments: true +BinPackParameters: true + +# Braces +BreakBeforeBraces: Attach +BreakBeforeBinaryOperators: None +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true + +# Spacing +ColumnLimit: 100 +CommentPragmas: "^ IWYU pragma:" +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 4 +DerivePointerAlignment: true +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH + +# Include sorting +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^' + Priority: 2 + - Regex: '^<.*\.h>' + Priority: 1 + - Regex: "^<.*" + Priority: 2 + - Regex: ".*" + Priority: 3 +IncludeIsMainRegex: "([-_](test|unittest))?$" +IndentCaseLabels: true +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: "" +MacroBlockEnd: "" +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: false +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 1 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 200 +PointerAlignment: Left +ReflowComments: true +SortIncludes: true +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Auto diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..a87c270 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,63 @@ +--- +# Configuration for clang-tidy +Checks: > + *, + -abseil-*, + -altera-*, + -android-*, + -fuchsia-*, + -google-*, + -llvm*, + -zircon-*, + -readability-else-after-return, + -readability-static-accessed-through-instance, + -readability-avoid-const-params-in-decls, + -cppcoreguidelines-non-private-member-variables-in-classes, + -misc-non-private-member-variables-in-classes, + -modernize-use-trailing-return-type, + -modernize-avoid-c-arrays, + -cppcoreguidelines-avoid-c-arrays, + -cppcoreguidelines-pro-bounds-constant-array-index, + -cppcoreguidelines-pro-bounds-array-to-pointer-decay, + -hicpp-no-array-decay, + -cppcoreguidelines-avoid-magic-numbers, + -readability-magic-numbers, + -cert-err34-c, + -bugprone-easily-swappable-parameters, + -readability-function-cognitive-complexity, + -cert-err58-cpp, + -cppcoreguidelines-owning-memory, + -cppcoreguidelines-avoid-non-const-global-variables, + -misc-use-anonymous-namespace, + -bugprone-exception-escape, + -cppcoreguidelines-avoid-const-or-ref-data-members + +CheckOptions: + - key: readability-identifier-naming.NamespaceCase + value: lower_case + - key: readability-identifier-naming.ClassCase + value: CamelCase + - key: readability-identifier-naming.StructCase + value: CamelCase + - key: readability-identifier-naming.FunctionCase + value: camelBack + - key: readability-identifier-naming.VariableCase + value: camelBack + - key: readability-identifier-naming.PrivateMemberSuffix + value: _ + - key: readability-identifier-naming.ProtectedMemberSuffix + value: _ + - key: readability-identifier-naming.MacroDefinitionCase + value: UPPER_CASE + - key: readability-identifier-naming.EnumConstantCase + value: CamelCase + - key: readability-identifier-naming.ConstexprVariableCase + value: CamelCase + - key: readability-identifier-naming.GlobalConstantCase + value: CamelCase + - key: readability-identifier-naming.MemberConstantCase + value: CamelCase + - key: readability-identifier-naming.StaticConstantCase + value: CamelCase + - key: misc-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic + value: true diff --git a/.github/workflows/linter-cpp.yml b/.github/workflows/linter-cpp.yml new file mode 100644 index 0000000..c62852f --- /dev/null +++ b/.github/workflows/linter-cpp.yml @@ -0,0 +1,49 @@ +--- +name: C++ Linter + +on: # yamllint disable-line rule:truthy + push: + branches: ["main"] + pull_request: + branches: ["main"] + +jobs: + lint-cpp: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y cmake build-essential clang-format \ + clang-tidy cppcheck + + - name: Install Google Test (required for compilation flags) + run: | + # Install Google Test from source - most reliable approach + git clone https://github.com/google/googletest.git --depth 1 + cd googletest + mkdir build && cd build + cmake .. -DCMAKE_INSTALL_PREFIX=/usr/local -DBUILD_SHARED_LIBS=ON + make -j$(nproc) + sudo make install + sudo ldconfig + + - name: Debug Google Test configuration + run: make debug-gtest + + - name: Lint C++ code using Makefile + run: | + make lint-cpp + + - name: Format check C++ code using Makefile + run: | + make format-cpp + # Check if any files were modified by formatting + if ! git diff --exit-code; then + echo "Code formatting issues found. Please run 'make \ + format-cpp' locally." + exit 1 + fi diff --git a/.github/workflows/linter-py.yml b/.github/workflows/linter-py.yml new file mode 100644 index 0000000..073c0d9 --- /dev/null +++ b/.github/workflows/linter-py.yml @@ -0,0 +1,43 @@ +--- +name: Python Linter + +on: # yamllint disable-line rule:truthy + push: + branches: ["main"] + pull_request: + branches: ["main"] + +jobs: + lint-python: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.10", "3.11", "3.12"] + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + cache: "pip" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Lint Python code using Makefile + run: | + make lint-python + + - name: Format check Python code using Makefile + run: | + make format-python + # Check if any files were modified by formatting + if ! git diff --exit-code; then + echo "Code formatting issues found. Please run 'make \ + format-python' locally." + exit 1 + fi diff --git a/.github/workflows/postsubmit-cpp.yml b/.github/workflows/postsubmit-cpp.yml new file mode 100644 index 0000000..6e3b049 --- /dev/null +++ b/.github/workflows/postsubmit-cpp.yml @@ -0,0 +1,40 @@ +--- +name: Postsubmit (C++) + +on: # yamllint disable-line rule:truthy + push: + branches: ["main"] + +jobs: + test-cpp: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y cmake build-essential + + - name: Install Google Test + run: | + # Install Google Test from source - most reliable approach + git clone https://github.com/google/googletest.git --depth 1 + cd googletest + mkdir build && cd build + cmake .. -DCMAKE_INSTALL_PREFIX=/usr/local -DBUILD_SHARED_LIBS=ON + make -j$(nproc) + sudo make install + sudo ldconfig + + - name: Debug Google Test configuration + run: make debug-gtest + + - name: Run all C++ tests using Makefile + run: | + make test-cpp:all + + - name: Show project statistics + run: | + make stats diff --git a/.github/workflows/postsubmit-py.yml b/.github/workflows/postsubmit-py.yml new file mode 100644 index 0000000..0c2e77e --- /dev/null +++ b/.github/workflows/postsubmit-py.yml @@ -0,0 +1,35 @@ +--- +name: Postsubmit (Python) + +on: # yamllint disable-line rule:truthy + push: + branches: ["main"] + +jobs: + test-python: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.10", "3.11", "3.12"] + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + cache: "pip" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Run all Python tests using Makefile + run: | + make test-py:all + + - name: Show project statistics + run: | + make stats diff --git a/.github/workflows/cpp-package.yml b/.github/workflows/presubmit-cpp.yml similarity index 97% rename from .github/workflows/cpp-package.yml rename to .github/workflows/presubmit-cpp.yml index a5eb86f..ecfd117 100644 --- a/.github/workflows/cpp-package.yml +++ b/.github/workflows/presubmit-cpp.yml @@ -89,10 +89,6 @@ jobs: [ -n "$COMMON_CHANGES" ] && echo 'true' || echo 'false')" >> \ $GITHUB_OUTPUT - - name: Format C++ code - if: steps.detect-changes.outputs.has_cpp_changes == 'true' - run: make format-cpp - - name: Lint C++ code if: steps.detect-changes.outputs.has_cpp_changes == 'true' run: make lint-cpp diff --git a/.github/workflows/python-package-conda.yml b/.github/workflows/presubmit-python.yml similarity index 97% rename from .github/workflows/python-package-conda.yml rename to .github/workflows/presubmit-python.yml index 37902bd..4995090 100644 --- a/.github/workflows/python-package-conda.yml +++ b/.github/workflows/presubmit-python.yml @@ -78,10 +78,6 @@ jobs: echo "should_run_all_tests=$([ -n "$PYTHON_CONFIG_CHANGES" ] && \ echo 'true' || echo 'false')" >> $GITHUB_OUTPUT - - name: Format Python code - if: steps.detect-changes.outputs.has_python_changes == 'true' - run: make format-python - - name: Lint Python code if: steps.detect-changes.outputs.has_python_changes == 'true' run: make lint-python diff --git a/README.md b/README.md index 3518b16..8b30a5a 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,10 @@ # Leetcode -[![Cpp Format & Test](https://github.com/mathusanm6/LeetCode/actions/workflows/cpp-package.yml/badge.svg)](https://github.com/mathusanm6/LeetCode/actions/workflows/cpp-package.yml) -[![Python CI (conda)](https://github.com/mathusanMe/LeetCode/actions/workflows/python-package-conda.yml/badge.svg)](https://github.com/mathusanMe/LeetCode/actions/workflows/python-package-conda.yml) +[![Code Health (C++)](https://github.com/mathusanm6/LeetCode/actions/workflows/postsubmit-cpp.yml/badge.svg)](https://github.com/mathusanm6/LeetCode/actions/workflows/postsubmit-cpp.yml) +[![C++ Linter](https://github.com/mathusanm6/LeetCode/actions/workflows/linter-cpp.yml/badge.svg)](https://github.com/mathusanm6/LeetCode/actions/workflows/linter-cpp.yml) + +[![Code Health (Python)](https://github.com/mathusanm6/LeetCode/actions/workflows/postsubmit-py.yml/badge.svg)](https://github.com/mathusanm6/LeetCode/actions/workflows/postsubmit-py.yml) +[![Python Linter](https://github.com/mathusanm6/LeetCode/actions/workflows/linter-py.yml/badge.svg)](https://github.com/mathusanm6/LeetCode/actions/workflows/linter-py.yml) ## Description @@ -11,22 +14,58 @@ This repository contains my solutions to LeetCode problems. I will be updating t ### Python Tests -To run the Python test suite, use the following command in **the repository directory**: +To run the Python test suite, use the following commands in **the repository directory**: ```bash -pytest python +# Run all Python tests +make test-py:all + +# Run tests for a specific problem (e.g., two-sum) +make test-py:two-sum ``` ### C++ Tests -To build and run the C++ test suite, use the following commands in the repository directory: +To run the C++ test suite, use the following commands in the repository directory: + +```bash +# Run all C++ tests +make test-cpp:all + +# Run tests for a specific problem (e.g., two-sum) +make test-cpp:two-sum + +# Run both C++ and Python tests for all problems +make test:all +``` + +## Code Quality + +This project includes comprehensive code quality tools and automated linting: + +### Linting and Formatting ```bash -cd cpp -make all -make run_tests +# Format all code (C++ and Python) +make format + +# Lint all code (C++ and Python) +make lint + +# Format/lint specific languages +make format-cpp # Format C++ files with clang-format +make format-python # Format Python files with ruff +make lint-cpp # Lint C++ files with clang-tidy +make lint-python # Lint Python files with ruff ``` +### Continuous Integration + +- **Linters**: Automated code formatting and linting checks on every push/PR +- **Presubmit**: Comprehensive testing of changed files before merge +- **Postsubmit**: Full test suite execution after merge to main branch +- All workflows leverage the project's Makefile for consistency + ## Algorithms - [Arrays & Hashing](#arrays--hashing) diff --git a/problems/two_sum/two_sum.cc b/problems/two_sum/two_sum.cc index 37e1c9e..5c22ddd 100644 --- a/problems/two_sum/two_sum.cc +++ b/problems/two_sum/two_sum.cc @@ -4,16 +4,16 @@ #include std::vector twoSum(const std::vector &nums, const int target) { - const int n = static_cast(nums.size()); - std::unordered_map index_of; - index_of.reserve(nums.size()); // Reserve space to avoid rehashing - for (int idx = 0; idx < n; ++idx) { - const int num = nums[idx]; - int need = target - num; - if (auto it = index_of.find(need); it != index_of.end()) { - return {it->second, idx}; + const int numCount = static_cast(nums.size()); + std::unordered_map indexOf; + indexOf.reserve(nums.size()); // Reserve space to avoid rehashing + for (int idx = 0; idx < numCount; ++idx) { + const int num = nums[idx]; + const int need = target - num; + if (auto iterator = indexOf.find(need); iterator != indexOf.end()) { + return {iterator->second, idx}; + } + indexOf.emplace(num, idx); // Note: no overwrite if x already exists } - index_of.emplace(num, idx); // Note: no overwrite if x already exists - } - return {-1, -1}; // If no solution is found + return {-1, -1}; // If no solution is found } diff --git a/problems/two_sum/two_sum_test.cc b/problems/two_sum/two_sum_test.cc index 1079d5d..83009c6 100644 --- a/problems/two_sum/two_sum_test.cc +++ b/problems/two_sum/two_sum_test.cc @@ -1,85 +1,64 @@ #include "two_sum.h" #include +#include #include struct TwoSumCase { - const std::string test_name; - const std::vector nums; - const int target; - const std::vector expected; + std::string test_name; + std::vector nums; + int target; + std::vector expected; }; using TwoSumTest = ::testing::TestWithParam; TEST_P(TwoSumTest, TestCases) { - const TwoSumCase &test_case = GetParam(); - const std::vector result = twoSum(test_case.nums, test_case.target); - EXPECT_EQ(result, test_case.expected); + const TwoSumCase &testCase = GetParam(); + const std::vector result = twoSum(testCase.nums, testCase.target); + EXPECT_EQ(result, testCase.expected); } INSTANTIATE_TEST_SUITE_P( TwoSumTestCases, TwoSumTest, - ::testing::Values(TwoSumCase{.test_name = "BasicCase", - .nums = {2, 7, 11, 15}, - .target = 9, - .expected = {0, 1}}, - TwoSumCase{.test_name = "MiddleElements", - .nums = {3, 2, 4}, - .target = 6, - .expected = {1, 2}}, - TwoSumCase{.test_name = "DuplicateElements", - .nums = {3, 3}, - .target = 6, - .expected = {0, 1}}, - TwoSumCase{.test_name = "NegativePositiveMix", - .nums = {-3, 1, 2, 3}, - .target = 0, - .expected = {0, 3}}, - TwoSumCase{.test_name = "NoSolution", - .nums = {0, 1}, - .target = -1, - .expected = {-1, -1}}, - TwoSumCase{.test_name = "TargetAtEnd", - .nums = {1, 2, 3, 4, 5}, - .target = 8, - .expected = {2, 4}}, - TwoSumCase{.test_name = "LargeNumbers", - .nums = {5, 75, 25}, - .target = 100, - .expected = {1, 2}}, - TwoSumCase{.test_name = "AllNegativeNumbers", - .nums = {-1, -2, -3, -4, -5}, - .target = -8, - .expected = {2, 4}}, - TwoSumCase{.test_name = "DuplicateZeros", - .nums = {0, 4, 3, 0}, - .target = 0, - .expected = {0, 3}}, - TwoSumCase{.test_name = "MultipleDuplicates", - .nums = {1, 1, 1, 1}, - .target = 2, - .expected = {0, 1}}, - TwoSumCase{.test_name = "SingleElementNoSolution", - .nums = {1}, - .target = 2, - .expected = {-1, -1}}, - TwoSumCase{.test_name = "TwoIdenticalElements", - .nums = {2, 2}, - .target = 4, - .expected = {0, 1}}, - TwoSumCase{.test_name = "LargePositiveNegative", - .nums = {1000, -1000}, - .target = 0, - .expected = {0, 1}}, - TwoSumCase{.test_name = "LargerArray", - .nums = {1, 2, 3, 4, 5, 6}, - .target = 11, - .expected = {4, 5}}, - TwoSumCase{.test_name = "TwoZerosSumToZero", - .nums = {0, 0}, - .target = 0, - .expected = {0, 1}}), - [](const ::testing::TestParamInfo &info) { - return info.param.test_name; - }); + ::testing::Values( + TwoSumCase{ + .test_name = "BasicCase", .nums = {2, 7, 11, 15}, .target = 9, .expected = {0, 1}}, + TwoSumCase{ + .test_name = "MiddleElements", .nums = {3, 2, 4}, .target = 6, .expected = {1, 2}}, + TwoSumCase{ + .test_name = "DuplicateElements", .nums = {3, 3}, .target = 6, .expected = {0, 1}}, + TwoSumCase{.test_name = "NegativePositiveMix", + .nums = {-3, 1, 2, 3}, + .target = 0, + .expected = {0, 3}}, + TwoSumCase{.test_name = "NoSolution", .nums = {0, 1}, .target = -1, .expected = {-1, -1}}, + TwoSumCase{ + .test_name = "TargetAtEnd", .nums = {1, 2, 3, 4, 5}, .target = 8, .expected = {2, 4}}, + TwoSumCase{ + .test_name = "LargeNumbers", .nums = {5, 75, 25}, .target = 100, .expected = {1, 2}}, + TwoSumCase{.test_name = "AllNegativeNumbers", + .nums = {-1, -2, -3, -4, -5}, + .target = -8, + .expected = {2, 4}}, + TwoSumCase{ + .test_name = "DuplicateZeros", .nums = {0, 4, 3, 0}, .target = 0, .expected = {0, 3}}, + TwoSumCase{.test_name = "MultipleDuplicates", + .nums = {1, 1, 1, 1}, + .target = 2, + .expected = {0, 1}}, + TwoSumCase{ + .test_name = "SingleElementNoSolution", .nums = {1}, .target = 2, .expected = {-1, -1}}, + TwoSumCase{ + .test_name = "TwoIdenticalElements", .nums = {2, 2}, .target = 4, .expected = {0, 1}}, + TwoSumCase{.test_name = "LargePositiveNegative", + .nums = {1000, -1000}, + .target = 0, + .expected = {0, 1}}, + TwoSumCase{.test_name = "LargerArray", + .nums = {1, 2, 3, 4, 5, 6}, + .target = 11, + .expected = {4, 5}}, + TwoSumCase{ + .test_name = "TwoZerosSumToZero", .nums = {0, 0}, .target = 0, .expected = {0, 1}}), + [](const ::testing::TestParamInfo &info) { return info.param.test_name; }); diff --git a/problems/valid_palindrome/valid_palindrome.cc b/problems/valid_palindrome/valid_palindrome.cc index 81a3b02..1e96c0c 100644 --- a/problems/valid_palindrome/valid_palindrome.cc +++ b/problems/valid_palindrome/valid_palindrome.cc @@ -2,34 +2,35 @@ #include #include +#include -bool isPalindrome(const std::string &s) { - int left = 0; - int right = static_cast(s.size()) - 1; +bool isPalindrome(const std::string &str) { + int left = 0; + int right = static_cast(str.size()) - 1; - // Create a copy of the string and transform to lowercase - std::string s_lower = s; - std::transform(s_lower.begin(), s_lower.end(), s_lower.begin(), - [](unsigned char c) { return std::tolower(c); }); + // Create a copy of the string and transform to lowercase + std::string lowerStr = str; + std::ranges::transform(lowerStr, lowerStr.begin(), + [](unsigned char character) { return std::tolower(character); }); - while (left < right) { - // Move left pointer to the next alphanumeric character - while (left < right && !std::isalnum(s_lower[left])) { - ++left; - } + while (left < right) { + // Move left pointer to the next alphanumeric character + while (left < right && std::isalnum(lowerStr[left]) == 0) { + ++left; + } - // Move right pointer to the previous alphanumeric character - while (left < right && !std::isalnum(s_lower[right])) { - --right; - } + // Move right pointer to the previous alphanumeric character + while (left < right && std::isalnum(lowerStr[right]) == 0) { + --right; + } - // Compare characters at left and right pointers - if (s_lower[left] != s_lower[right]) { - return false; // Not a palindrome - } + // Compare characters at left and right pointers + if (lowerStr[left] != lowerStr[right]) { + return false; // Not a palindrome + } - ++left; - --right; - } - return true; // Is a palindrome + ++left; + --right; + } + return true; // Is a palindrome } \ No newline at end of file diff --git a/problems/valid_palindrome/valid_palindrome.h b/problems/valid_palindrome/valid_palindrome.h index 9484c47..c09aadd 100644 --- a/problems/valid_palindrome/valid_palindrome.h +++ b/problems/valid_palindrome/valid_palindrome.h @@ -1,3 +1,3 @@ #include -bool isPalindrome(const std::string &s); \ No newline at end of file +bool isPalindrome(const std::string &str); \ No newline at end of file diff --git a/problems/valid_palindrome/valid_palindrome_test.cc b/problems/valid_palindrome/valid_palindrome_test.cc index c90b56e..8276ae2 100644 --- a/problems/valid_palindrome/valid_palindrome_test.cc +++ b/problems/valid_palindrome/valid_palindrome_test.cc @@ -4,94 +4,62 @@ #include struct ValidPalindromeCase { - const std::string test_name; - const std::string s; - const bool expected; + std::string test_name; + std::string str; + bool expected; }; using ValidPalindromeTest = ::testing::TestWithParam; TEST_P(ValidPalindromeTest, TestCases) { - const ValidPalindromeCase &test_case = GetParam(); - const bool result = isPalindrome(test_case.s); - EXPECT_EQ(result, test_case.expected); + const ValidPalindromeCase &testCase = GetParam(); + const bool result = isPalindrome(testCase.str); + EXPECT_EQ(result, testCase.expected); } INSTANTIATE_TEST_SUITE_P( ValidPalindromeTestCases, ValidPalindromeTest, ::testing::Values( ValidPalindromeCase{.test_name = "ClassicPanama", - .s = "A man, a plan, a canal: Panama", + .str = "A man, a plan, a canal: Panama", .expected = true}, + ValidPalindromeCase{.test_name = "RaceCarFalse", .str = "race a car", .expected = false}, + ValidPalindromeCase{.test_name = "EmptyString", .str = "", .expected = true}, + ValidPalindromeCase{.test_name = "SingleChar", .str = "a", .expected = true}, + ValidPalindromeCase{.test_name = "CaseInsensitive", .str = "Aa", .expected = true}, + ValidPalindromeCase{.test_name = "TwoCharFalse", .str = "ab", .expected = false}, + ValidPalindromeCase{.test_name = "TwoCharTrue", .str = "aa", .expected = true}, + ValidPalindromeCase{.test_name = "SimplePalindrome", .str = "racecar", .expected = true}, + ValidPalindromeCase{.test_name = "SimpleFalse", .str = "hello", .expected = false}, + ValidPalindromeCase{.test_name = "SantaNasa", .str = "A Santa at NASA", .expected = true}, ValidPalindromeCase{ - .test_name = "RaceCarFalse", .s = "race a car", .expected = false}, + .test_name = "CarCatQuestion", .str = "Was it a car or a cat I saw?", .expected = true}, ValidPalindromeCase{ - .test_name = "EmptyString", .s = "", .expected = true}, + .test_name = "NixonApostrophe", .str = "No 'x' in Nixon", .expected = true}, + ValidPalindromeCase{.test_name = "MadamAdam", .str = "Madam, I'm Adam", .expected = true}, ValidPalindromeCase{ - .test_name = "SingleChar", .s = "a", .expected = true}, - ValidPalindromeCase{ - .test_name = "CaseInsensitive", .s = "Aa", .expected = true}, - ValidPalindromeCase{ - .test_name = "TwoCharFalse", .s = "ab", .expected = false}, - ValidPalindromeCase{ - .test_name = "TwoCharTrue", .s = "aa", .expected = true}, - ValidPalindromeCase{ - .test_name = "SimplePalindrome", .s = "racecar", .expected = true}, - ValidPalindromeCase{ - .test_name = "SimpleFalse", .s = "hello", .expected = false}, - ValidPalindromeCase{ - .test_name = "SantaNasa", .s = "A Santa at NASA", .expected = true}, - ValidPalindromeCase{.test_name = "CarCatQuestion", - .s = "Was it a car or a cat I saw?", - .expected = true}, - ValidPalindromeCase{.test_name = "NixonApostrophe", - .s = "No 'x' in Nixon", + .test_name = "NeverOddEven", .str = "never odd or even", .expected = true}, + ValidPalindromeCase{.test_name = "NopeFalse", .str = "nope", .expected = false}, + ValidPalindromeCase{.test_name = "MixedAlphanumFalse", .str = "0P", .expected = false}, + ValidPalindromeCase{.test_name = "PanamaExclamation", + .str = "A man, a plan, a canal: Panama!", .expected = true}, + ValidPalindromeCase{.test_name = "RaceEcarHyphen", .str = "race a E-car", .expected = true}, ValidPalindromeCase{ - .test_name = "MadamAdam", .s = "Madam, I'm Adam", .expected = true}, - ValidPalindromeCase{.test_name = "NeverOddEven", - .s = "never odd or even", - .expected = true}, + .test_name = "AbleElba", .str = "Able was I ere I saw Elba", .expected = true}, + ValidPalindromeCase{.test_name = "NumericPalindrome", .str = "12321", .expected = true}, + ValidPalindromeCase{.test_name = "NumericFalse", .str = "12345", .expected = false}, ValidPalindromeCase{ - .test_name = "NopeFalse", .s = "nope", .expected = false}, + .test_name = "AlphanumPalindrome", .str = "a1b2c3c2b1a", .expected = true}, + ValidPalindromeCase{.test_name = "AlphanumFalse", .str = "1a2b3c3c2b1a", .expected = false}, ValidPalindromeCase{ - .test_name = "MixedAlphanumFalse", .s = "0P", .expected = false}, - ValidPalindromeCase{.test_name = "PanamaExclamation", - .s = "A man, a plan, a canal: Panama!", - .expected = true}, - ValidPalindromeCase{.test_name = "RaceEcarHyphen", - .s = "race a E-car", - .expected = true}, - ValidPalindromeCase{.test_name = "AbleElba", - .s = "Able was I ere I saw Elba", - .expected = true}, + .test_name = "OnlySpecialChars", .str = ".,!@#$%^&*()", .expected = true}, ValidPalindromeCase{ - .test_name = "NumericPalindrome", .s = "12321", .expected = true}, + .test_name = "SpecialCharsMiddle", .str = "a.,!@#$%^&*()a", .expected = true}, + ValidPalindromeCase{.test_name = "DammitMad", .str = "Dammit, I'm mad!", .expected = true}, + ValidPalindromeCase{.test_name = "StepPets", .str = "Step on no pets", .expected = true}, ValidPalindromeCase{ - .test_name = "NumericFalse", .s = "12345", .expected = false}, - ValidPalindromeCase{.test_name = "AlphanumPalindrome", - .s = "a1b2c3c2b1a", - .expected = true}, - ValidPalindromeCase{.test_name = "AlphanumFalse", - .s = "1a2b3c3c2b1a", - .expected = false}, - ValidPalindromeCase{.test_name = "OnlySpecialChars", - .s = ".,!@#$%^&*()", - .expected = true}, - ValidPalindromeCase{.test_name = "SpecialCharsMiddle", - .s = "a.,!@#$%^&*()a", - .expected = true}, - ValidPalindromeCase{.test_name = "DammitMad", - .s = "Dammit, I'm mad!", - .expected = true}, + .test_name = "RatQuestion", .str = "Was it a rat I saw?", .expected = true}, ValidPalindromeCase{ - .test_name = "StepPets", .s = "Step on no pets", .expected = true}, - ValidPalindromeCase{.test_name = "RatQuestion", - .s = "Was it a rat I saw?", - .expected = true}, - ValidPalindromeCase{.test_name = "OwlWorm", - .s = "Mr. Owl ate my metal worm", - .expected = true}), - [](const ::testing::TestParamInfo &info) { - return info.param.test_name; - }); \ No newline at end of file + .test_name = "OwlWorm", .str = "Mr. Owl ate my metal worm", .expected = true}), + [](const ::testing::TestParamInfo &info) { return info.param.test_name; }); \ No newline at end of file From 6752861ce13e8688c8c53535bc8d0be7ccb8c126 Mon Sep 17 00:00:00 2001 From: mathusanm6 Date: Fri, 29 Aug 2025 17:17:43 +0200 Subject: [PATCH 2/4] fix: linter only on push to main --- .github/workflows/linter-cpp.yml | 2 -- .github/workflows/linter-py.yml | 2 -- 2 files changed, 4 deletions(-) diff --git a/.github/workflows/linter-cpp.yml b/.github/workflows/linter-cpp.yml index c62852f..7ae56e3 100644 --- a/.github/workflows/linter-cpp.yml +++ b/.github/workflows/linter-cpp.yml @@ -4,8 +4,6 @@ name: C++ Linter on: # yamllint disable-line rule:truthy push: branches: ["main"] - pull_request: - branches: ["main"] jobs: lint-cpp: diff --git a/.github/workflows/linter-py.yml b/.github/workflows/linter-py.yml index 073c0d9..ecddd9b 100644 --- a/.github/workflows/linter-py.yml +++ b/.github/workflows/linter-py.yml @@ -4,8 +4,6 @@ name: Python Linter on: # yamllint disable-line rule:truthy push: branches: ["main"] - pull_request: - branches: ["main"] jobs: lint-python: From 00ad1c980cccc64c50d522573b5b02b00b6f80c2 Mon Sep 17 00:00:00 2001 From: mathusanm6 Date: Fri, 29 Aug 2025 17:21:08 +0200 Subject: [PATCH 3/4] fix: only run install dependencies if files changed for each language --- .github/workflows/presubmit-cpp.yml | 40 +++++++++++++++----------- .github/workflows/presubmit-python.yml | 28 +++++++++++------- 2 files changed, 41 insertions(+), 27 deletions(-) diff --git a/.github/workflows/presubmit-cpp.yml b/.github/workflows/presubmit-cpp.yml index ecfd117..53c8af3 100644 --- a/.github/workflows/presubmit-cpp.yml +++ b/.github/workflows/presubmit-cpp.yml @@ -14,22 +14,6 @@ jobs: with: fetch-depth: 0 # Fetch full history for git diff - - name: Install dependencies - run: | - sudo apt-get update - sudo apt-get install -y cmake build-essential clang-format clang-tidy - # Install Google Test from source - most reliable approach - git clone https://github.com/google/googletest.git --depth 1 - cd googletest - mkdir build && cd build - cmake .. -DCMAKE_INSTALL_PREFIX=/usr/local -DBUILD_SHARED_LIBS=ON - make -j$(nproc) - sudo make install - sudo ldconfig - - - name: Debug Google Test configuration - run: make debug-gtest - - name: Detect changed C++ files id: detect-changes run: | @@ -89,6 +73,30 @@ jobs: [ -n "$COMMON_CHANGES" ] && echo 'true' || echo 'false')" >> \ $GITHUB_OUTPUT + - name: Install dependencies + if: > + steps.detect-changes.outputs.has_cpp_changes == 'true' || + steps.detect-changes.outputs.has_cpp_config_changes == 'true' || + steps.detect-changes.outputs.has_common_changes == 'true' + run: | + sudo apt-get update + sudo apt-get install -y cmake build-essential clang-format clang-tidy + # Install Google Test from source - most reliable approach + git clone https://github.com/google/googletest.git --depth 1 + cd googletest + mkdir build && cd build + cmake .. -DCMAKE_INSTALL_PREFIX=/usr/local -DBUILD_SHARED_LIBS=ON + make -j$(nproc) + sudo make install + sudo ldconfig + + - name: Debug Google Test configuration + if: > + steps.detect-changes.outputs.has_cpp_changes == 'true' || + steps.detect-changes.outputs.has_cpp_config_changes == 'true' || + steps.detect-changes.outputs.has_common_changes == 'true' + run: make debug-gtest + - name: Lint C++ code if: steps.detect-changes.outputs.has_cpp_changes == 'true' run: make lint-cpp diff --git a/.github/workflows/presubmit-python.yml b/.github/workflows/presubmit-python.yml index 4995090..eff4e9a 100644 --- a/.github/workflows/presubmit-python.yml +++ b/.github/workflows/presubmit-python.yml @@ -17,17 +17,6 @@ jobs: with: fetch-depth: 0 # Fetch full history for git diff - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - cache: "pip" - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -r requirements.txt - - name: Detect changed Python files id: detect-changes run: | @@ -78,6 +67,23 @@ jobs: echo "should_run_all_tests=$([ -n "$PYTHON_CONFIG_CHANGES" ] && \ echo 'true' || echo 'false')" >> $GITHUB_OUTPUT + - name: Set up Python ${{ matrix.python-version }} + if: > + steps.detect-changes.outputs.has_python_changes == 'true' || + steps.detect-changes.outputs.has_python_config_changes == 'true' + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + cache: "pip" + + - name: Install dependencies + if: > + steps.detect-changes.outputs.has_python_changes == 'true' || + steps.detect-changes.outputs.has_python_config_changes == 'true' + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + - name: Lint Python code if: steps.detect-changes.outputs.has_python_changes == 'true' run: make lint-python From a3413af0817ee99b1433ab1c3c3753b8c44acc2c Mon Sep 17 00:00:00 2001 From: mathusanm6 Date: Fri, 29 Aug 2025 17:32:29 +0200 Subject: [PATCH 4/4] fix: clang tidy is much more quiet --- .clang-tidy | 4 ++++ Makefile | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.clang-tidy b/.clang-tidy index a87c270..ecc6b8e 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -12,6 +12,7 @@ Checks: > -readability-else-after-return, -readability-static-accessed-through-instance, -readability-avoid-const-params-in-decls, + -readability-identifier-length, -cppcoreguidelines-non-private-member-variables-in-classes, -misc-non-private-member-variables-in-classes, -modernize-use-trailing-return-type, @@ -32,6 +33,9 @@ Checks: > -bugprone-exception-escape, -cppcoreguidelines-avoid-const-or-ref-data-members +# Exclude Google Test and external dependencies from analysis +HeaderFilterRegex: "^(?!.*(gtest|googletest|gmock|third_party|external|vendor|/opt/homebrew/)).*" + CheckOptions: - key: readability-identifier-naming.NamespaceCase value: lower_case diff --git a/Makefile b/Makefile index 9b51147..d324fd5 100644 --- a/Makefile +++ b/Makefile @@ -209,7 +209,8 @@ lint-cpp: @echo "Linting C++ files..." @if command -v clang-tidy >/dev/null; then \ find $(PROBLEMS_DIR) $(COMMON_DIR) -type f \( -name '*.cc' -o -name '*.h' \) | while read -r file; do \ - clang-tidy "$$file" -- $(CXXFLAGS) -x c++ || true; \ + clang-tidy "$$file" --config-file=.clang-tidy --quiet -- $(CXXFLAGS) -x c++ 2>&1 | \ + grep -v "warnings generated\|Suppressed.*warnings\|Use -header-filter\|Use -system-headers" || true; \ done; \ echo "$(call color_green,C++ linting complete.)"; \ else \