diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index 2db2c5d..0000000 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,15 +0,0 @@ -### Description - - - -### Type of Change - -- [ ] Bug fix -- [ ] New feature -- [ ] Core update -- [ ] Tests added -- [ ] Documentation update - - -### Additional Notes - diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..25a93ee --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,35 @@ +### Pull Request Guidelines + +1. While creating Pull Request from feature to release branch, please provide proper description and check the boxes for specific changes made in this feature branch. +2. While creating Pull Request from release to main branch, please check the appropriate version bump from the provided list. + +--- +### PR from feature to release + +**Description** + + +**Type of Change** + +- [ ] Bug fix +- [ ] New feature +- [ ] Core update +- [ ] Tests added +- [ ] Documentation update + +**Notes** + + +---- +### PR from release to main + +**Release Summary** + + +**Version Bump Type** +- [ ] Major +- [ ] Minor +- [ ] Patch + +**Notes** + diff --git a/.github/workflows/datastructures-algorithms-deploy.yaml b/.github/workflows/datastructures-algorithms-deploy.yaml new file mode 100644 index 0000000..cca4a5b --- /dev/null +++ b/.github/workflows/datastructures-algorithms-deploy.yaml @@ -0,0 +1,164 @@ +name: datastructures-algorithms-deploy + +# Triggers only when a PR to 'main' is closed +on: + pull_request: + types: [closed] # Run when a PR is closed + branches: + - 'main' # Only when the PR's target branch is 'main' + +# Permissions required for the tag/release job +permissions: + contents: write # To create/push tags AND create releases + pull-requests: read # To read the PR body + +jobs: + # --- Job 1: Create Tag and Release --- + create-tag-and-release: + + # This job only runs if: + # 1. The PR was actually merged (implicit from 'needs', but good to be explicit). + # 2. The PR was from a branch named exactly 'release'. + if: github.event.pull_request.merged == true && github.event.pull_request.head.ref == 'release' + runs-on: ubuntu-latest + + steps: + # Step 1: Check out the code on the 'main' branch + # fetch-depth: 0 is crucial to get all tags and history + - name: Checkout main branch + uses: actions/checkout@v4 + with: + ref: 'main' + fetch-depth: 0 + + # Step 2: Configure Git with bot credentials + - name: Configure Git + run: | + git config --global user.name "github-actions[bot]" + git config --global user.email "github-actions[bot]@users.noreply.github.com" + + # Step 3: Parse the PR body to find the version bump type + - name: Determine Version Bump + id: version_bump + env: + PR_BODY: ${{ github.event.pull_request.body }} + run: | + if echo "$PR_BODY" | grep -q '\[x\] Major'; then + echo "bump_type=major" >> $GITHUB_OUTPUT + elif echo "$PR_BODY" | grep -q '\[x\] Minor'; then + echo "bump_type=minor" >> $GITHUB_OUTPUT + elif echo "$PR_BODY" | grep -q '\[x\] Patch'; then + echo "bump_type=patch" >> $GITHUB_OUTPUT + else + echo "No version bump type selected in PR body. ([x] Major, [x] Minor, or [x] Patch)." + exit 1 + fi + + # Step 4: Get the latest tag + - name: Get latest tag + id: last_tag + run: | + # Fetch all tags from remote just in case + git fetch --tags + + # Find the latest tag matching vX.Y.Z format, sort by version, get the highest + LATEST_TAG=$(git tag --list 'v*.*.*' --sort=-v:refname | head -n 1) + + if [ -z "$LATEST_TAG" ]; then + echo "No previous vX.Y.Z tag found. Starting from v0.0.0." + LATEST_TAG="v0.0.0" + fi + + echo "Last tag: $LATEST_TAG" + echo "last_tag=$LATEST_TAG" >> $GITHUB_OUTPUT + + # Step 5: Calculate the new tag based on the bump type + - name: Calculate new tag + id: new_tag + run: | + BUMP_TYPE=${{ steps.version_bump.outputs.bump_type }} + LAST_TAG=${{ steps.last_tag.outputs.last_tag }} + + # Strip 'v' prefix + VERSION=${LAST_TAG#v} + + # Split into parts + IFS='.' read -r -a parts <<< "$VERSION" + MAJOR=${parts[0]} + MINOR=${parts[1]} + PATCH=${parts[2]} + + # Increment based on bump type + case "$BUMP_TYPE" in + "major") + MAJOR=$((MAJOR + 1)) + MINOR=0 + PATCH=0 + ;; + "minor") + MINOR=$((MINOR + 1)) + PATCH=0 + ;; + "patch") + PATCH=$((PATCH + 1)) + ;; + esac + + NEW_TAG="v${MAJOR}.${MINOR}.${PATCH}" + echo "new_tag=$NEW_TAG" >> $GITHUB_OUTPUT + echo "New tag will be: $NEW_TAG" + + # Step 6: Create and push the new tag + - name: Create and push new tag + run: | + NEW_TAG=${{ steps.new_tag.outputs.new_tag }} + MERGE_COMMIT_SHA=${{ github.event.pull_request.merge_commit_sha }} + + echo "Tagging commit $MERGE_COMMIT_SHA as $NEW_TAG" + git tag $NEW_TAG $MERGE_COMMIT_SHA + git push origin $NEW_TAG + + # Step 7: Extract Release Summary from PR Body + - name: Extract Release Summary + id: extract_summary + env: + PR_BODY: ${{ github.event.pull_request.body }} + run: | + # Use an awk "state machine" to extract text between the two markers. + # We use "<<<" (a "here string") to safely pass the multiline $PR_BODY to awk. + # We must escape the '*' characters with '\' for the regex. + + # 1. /\*\*Release Summary\*\*/{f=1; next} : When we see the START marker, set flag 'f' to 1 and skip to the next line. + # 2. /\*\*Version Bump Type\*\*/{f=0} : When we see the END marker, set flag 'f' to 0. + # 3. f : If flag 'f' is 1 (true), print the current line. + SUMMARY=$(awk '/\*\*Release Summary\*\*/{f=1; next} /\*\*Version Bump Type\*\*/{f=0} f' <<< "$PR_BODY") + + # Use multiline string syntax for GITHUB_OUTPUT + echo "summary<> $GITHUB_OUTPUT + echo "$SUMMARY" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + # Step 8: Create GitHub Release + - name: Create GitHub Release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NEW_TAG: ${{ steps.new_tag.outputs.new_tag }} + SUMMARY: ${{ steps.extract_summary.outputs.summary }} + PR_TITLE: ${{ github.event.pull_request.title }} + run: | + # Check if summary is empty, and provide a default + if [ -z "$SUMMARY" ]; then + echo "Release summary was empty. Using PR title as notes." + RELEASE_NOTES="$PR_TITLE" + else + RELEASE_NOTES="$SUMMARY" + fi + + # Create the release using the GitHub CLI + # --latest: Marks this as the latest official release + # --title: Set to just the tag name as requested + gh release create $NEW_TAG \ + --title "$NEW_TAG" \ + --notes "$RELEASE_NOTES" \ + --target ${{ github.event.pull_request.merge_commit_sha }} \ + --latest \ No newline at end of file diff --git a/.github/workflows/datastructures-algorithms-ci-cd.yaml b/.github/workflows/datastructures-algorithms-validate.yaml similarity index 62% rename from .github/workflows/datastructures-algorithms-ci-cd.yaml rename to .github/workflows/datastructures-algorithms-validate.yaml index 9c8f0e2..6ade440 100644 --- a/.github/workflows/datastructures-algorithms-ci-cd.yaml +++ b/.github/workflows/datastructures-algorithms-validate.yaml @@ -1,18 +1,27 @@ -name: datastructures-algorithms-ci-cd +name: datastructures-algorithms-validate +# Triggers when a PR is opened or updated, targeting the 'release' branch on: - push: - branches: [ main, release ] pull_request: - branches: [ main, release ] + types: [opened, synchronize] + branches: + - 'release' + - 'main' jobs: + # --- Job 1: Lint, Build, and Test --- lint-build-test: + + # This job will ONLY run if: + # 1. (Source is 'feature-*' AND Target is 'release') + # OR + # 2. (Source is 'release' AND Target is 'main') + if: (startsWith(github.event.pull_request.head.ref, 'feature-') && github.event.pull_request.base.ref == 'release') || (github.event.pull_request.head.ref == 'release' && github.event.pull_request.base.ref == 'main') runs-on: windows-latest steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 # Use v4 - name: Setup MSVC uses: ilammy/msvc-dev-cmd@v1 diff --git a/include/0005_DynamicProgramming/0008_TilingProblem.h b/include/0005_DynamicProgramming/0008_TilingProblem.h new file mode 100644 index 0000000..b1646fb --- /dev/null +++ b/include/0005_DynamicProgramming/0008_TilingProblem.h @@ -0,0 +1,25 @@ +#pragma once + +#include +using namespace std; + +/* +Pattern 1 +Linear Recurrence + +Description +Given a "2 x n" board and tiles of size "2 x 1", the task is to count the number of ways to tile the given board using the 2 x 1 tiles. +A tile can either be placed horizontally i.e., as a 1 x 2 tile or vertically i.e., as 2 x 1 tile. +*/ + +namespace TilingProblem +{ + class DynamicProgramming + { + private: + int NumberOfWaysRecursiveHelper(int n); + public: + int RecursiveNumberOfWays(int n); + int DpNumberOfWays(int n); + }; +} \ No newline at end of file diff --git a/source/0005_DynamicProgramming/0008_TilingProblem.cc b/source/0005_DynamicProgramming/0008_TilingProblem.cc new file mode 100644 index 0000000..ccb37c5 --- /dev/null +++ b/source/0005_DynamicProgramming/0008_TilingProblem.cc @@ -0,0 +1,42 @@ +#include "../../include/0005_DynamicProgramming/0008_TilingProblem.h" + +namespace TilingProblem +{ + int DynamicProgramming::NumberOfWaysRecursiveHelper(int n) + { + if (n < 0) + { + return 0; + } + + if (n == 0) + { + return 1; + } + + int result = 0; + result += this->NumberOfWaysRecursiveHelper(n - 1); + result += this->NumberOfWaysRecursiveHelper(n - 2); + + return result; + } + + int DynamicProgramming::RecursiveNumberOfWays(int n) + { + return this->NumberOfWaysRecursiveHelper(n); + } + + int DynamicProgramming::DpNumberOfWays(int n) + { + vector dp(n + 1, 0); + dp[0] = 1; + dp[1] = 1; + + for (int i = 2; i <= n; i++) + { + dp[i] = dp[i - 1] + dp[i - 2]; + } + + return dp[n]; + } +} \ No newline at end of file diff --git a/source/0005_DynamicProgramming/CMakeLists.txt b/source/0005_DynamicProgramming/CMakeLists.txt index c8a87c5..875e985 100644 --- a/source/0005_DynamicProgramming/CMakeLists.txt +++ b/source/0005_DynamicProgramming/CMakeLists.txt @@ -7,6 +7,7 @@ set(0005DYNAMICPROGRAMMING_SOURCES 0005_HouseRobber1.cc 0006_HouseRobber2.cc 0007_DecodeWays.cc + 0008_TilingProblem.cc ) diff --git a/test/0005_DynamicProgramming/0008_TilingProblemTest.cc b/test/0005_DynamicProgramming/0008_TilingProblemTest.cc new file mode 100644 index 0000000..dbff35e --- /dev/null +++ b/test/0005_DynamicProgramming/0008_TilingProblemTest.cc @@ -0,0 +1,33 @@ +#include +#include "../../include/0005_DynamicProgramming/0008_TilingProblem.h" + +namespace TilingProblem +{ + TEST(TilingProblem, RecursionTest01) + { + // Arrange + DynamicProgramming dp; + int nummberOfRows = 4; + int expectedNumberOfWays = 5; + + // Act + int actualNumberOfWays = dp.RecursiveNumberOfWays(nummberOfRows); + + // Assert + ASSERT_EQ(expectedNumberOfWays, actualNumberOfWays); + } + + TEST(TilingProblem, DpTest01) + { + // Arrange + DynamicProgramming dp; + int nummberOfRows = 4; + int expectedNumberOfWays = 5; + + // Act + int actualNumberOfWays = dp.DpNumberOfWays(nummberOfRows); + + // Assert + ASSERT_EQ(expectedNumberOfWays, actualNumberOfWays); + } +} \ No newline at end of file diff --git a/test/0005_DynamicProgramming/CMakeLists.txt b/test/0005_DynamicProgramming/CMakeLists.txt index 2e44016..a22ed15 100644 --- a/test/0005_DynamicProgramming/CMakeLists.txt +++ b/test/0005_DynamicProgramming/CMakeLists.txt @@ -21,6 +21,7 @@ add_executable( 0005_HouseRobber1Test.cc 0006_HouseRobber2Test.cc 0007_DecodeWaysTest.cc + 0008_TilingProblemTest.cc )