From c0c64b9f5f94d3c7e0b7b9f937a6e310e17f5896 Mon Sep 17 00:00:00 2001 From: Hardik Pawar Date: Wed, 9 Oct 2024 11:30:50 +0530 Subject: [PATCH 1/4] Add tests, add `IllegalArgumentException`, remove `main` in `WineProblem` --- .../dynamicprogramming/WineProblem.java | 70 +++++++++++------ .../dynamicprogramming/WineProblemTest.java | 78 +++++++++++++++++++ 2 files changed, 125 insertions(+), 23 deletions(-) create mode 100644 src/test/java/com/thealgorithms/dynamicprogramming/WineProblemTest.java diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/WineProblem.java b/src/main/java/com/thealgorithms/dynamicprogramming/WineProblem.java index 0f5359f4d95e..022b43184a88 100644 --- a/src/main/java/com/thealgorithms/dynamicprogramming/WineProblem.java +++ b/src/main/java/com/thealgorithms/dynamicprogramming/WineProblem.java @@ -1,21 +1,40 @@ package com.thealgorithms.dynamicprogramming; /** - * Imagine you have a collection of N wines placed next to each other on the - * shelf. The price of ith wine is pi(Prices of different wines are different). - * Because wine gets better every year supposing today is year 1, on year y the - * price would be y*pi i.e y times the value of the initial year. You want to - * sell all wines but you have to sell one wine per year. One more constraint on - * each year you are allowed to sell either leftmost or rightmost wine on the - * shelf. You are not allowed to reorder. You have to find the maximum profit + * The WineProblem class provides a solution to the wine selling problem. + * Given a collection of N wines with different prices, the objective is to maximize profit by selling + * one wine each year, considering the constraint that only the leftmost or rightmost wine can be sold + * at any given time. * + * The price of the ith wine is pi, and the selling price increases by a factor of the year in which + * it is sold. This class implements three approaches to solve the problem: + * + * 1. **Recursion**: A straightforward recursive method that computes the maximum profit. + * - Time Complexity: O(2^N) + * - Space Complexity: O(N) due to recursive calls. + * + * 2. **Top-Down Dynamic Programming (Memoization)**: This approach caches the results of subproblems + * to avoid redundant computations. + * - Time Complexity: O(N^2) + * - Space Complexity: O(N^2) for the storage of results and O(N) for recursion stack. + * + * 3. **Bottom-Up Dynamic Programming (Tabulation)**: This method builds a table iteratively to + * compute the maximum profit for all possible subproblems. + * - Time Complexity: O(N^2) + * - Space Complexity: O(N^2) for the table. */ public final class WineProblem { private WineProblem() { } - // Method 1: Using Recursion - // Time Complexity=0(2^N) Space Complexity=Recursion extra space + /** + * Calculate maximum profit using recursion. + * + * @param arr Array of wine prices. + * @param si Start index of the wine to consider. + * @param ei End index of the wine to consider. + * @return Maximum profit obtainable by selling the wines. + */ public static int wpRecursion(int[] arr, int si, int ei) { int n = arr.length; int year = (n - (ei - si + 1)) + 1; @@ -29,8 +48,15 @@ public static int wpRecursion(int[] arr, int si, int ei) { return Math.max(start, end); } - // Method 2: Top-Down DP(Memoization) - // Time Complexity=0(N*N) Space Complexity=0(N*N)+Recursion extra space + /** + * Calculate maximum profit using top-down dynamic programming with memoization. + * + * @param arr Array of wine prices. + * @param si Start index of the wine to consider. + * @param ei End index of the wine to consider. + * @param strg 2D array to store results of subproblems. + * @return Maximum profit obtainable by selling the wines. + */ public static int wptd(int[] arr, int si, int ei, int[][] strg) { int n = arr.length; int year = (n - (ei - si + 1)) + 1; @@ -45,15 +71,22 @@ public static int wptd(int[] arr, int si, int ei, int[][] strg) { int end = wptd(arr, si, ei - 1, strg) + arr[ei] * year; int ans = Math.max(start, end); - strg[si][ei] = ans; return ans; } - // Method 3: Bottom-Up DP(Tabulation) - // Time Complexity=0(N*N/2)->0(N*N) Space Complexity=0(N*N) + /** + * Calculate maximum profit using bottom-up dynamic programming with tabulation. + * + * @param arr Array of wine prices. + * @throws IllegalArgumentException if the input array is null or empty. + * @return Maximum profit obtainable by selling the wines. + */ public static int wpbu(int[] arr) { + if (arr == null || arr.length == 0) { + throw new IllegalArgumentException("Input array cannot be null or empty."); + } int n = arr.length; int[][] strg = new int[n][n]; @@ -73,13 +106,4 @@ public static int wpbu(int[] arr) { } return strg[0][n - 1]; } - - public static void main(String[] args) { - int[] arr = {2, 3, 5, 1, 4}; - System.out.println("Method 1: " + wpRecursion(arr, 0, arr.length - 1)); - System.out.println("Method 2: " + wptd(arr, 0, arr.length - 1, new int[arr.length][arr.length])); - System.out.println("Method 3: " + wpbu(arr)); - } } -// Memoization vs Tabulation : https://www.geeksforgeeks.org/tabulation-vs-memoization/ -// Question Link : https://www.geeksforgeeks.org/maximum-profit-sale-wines/ diff --git a/src/test/java/com/thealgorithms/dynamicprogramming/WineProblemTest.java b/src/test/java/com/thealgorithms/dynamicprogramming/WineProblemTest.java new file mode 100644 index 000000000000..ddde9203b60c --- /dev/null +++ b/src/test/java/com/thealgorithms/dynamicprogramming/WineProblemTest.java @@ -0,0 +1,78 @@ +package com.thealgorithms.dynamicprogramming; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +/** + * Unit tests for the WineProblem class. + * This test class verifies the correctness of the wine selling problem solutions. + */ +class WineProblemTest { + + /** + * Test for wpRecursion method. + */ + @Test + void testWpRecursion() { + int[] wines = {2, 3, 5, 1, 4}; // Prices of wines + int expectedProfit = 50; // The expected maximum profit + assertEquals(expectedProfit, WineProblem.wpRecursion(wines, 0, wines.length - 1), + "The maximum profit using recursion should be 50."); + } + + /** + * Test for wptd method (Top-Down DP with Memoization). + */ + @Test + void testWptd() { + int[] wines = {2, 3, 5, 1, 4}; // Prices of wines + int expectedProfit = 50; // The expected maximum profit + assertEquals(expectedProfit, WineProblem.wptd(wines, 0, wines.length - 1, new int[wines.length][wines.length]), + "The maximum profit using top-down DP should be 50."); + } + + /** + * Test for wpbu method (Bottom-Up DP with Tabulation). + */ + @Test + void testWpbu() { + int[] wines = {2, 3, 5, 1, 4}; // Prices of wines + int expectedProfit = 50; // The expected maximum profit + assertEquals(expectedProfit, WineProblem.wpbu(wines), + "The maximum profit using bottom-up DP should be 50."); + } + + /** + * Test with a single wine. + */ + @Test + void testSingleWine() { + int[] wines = {10}; // Only one wine + int expectedProfit = 10; // Selling the only wine at year 1 + assertEquals(expectedProfit, WineProblem.wpbu(wines), + "The maximum profit for a single wine should be 10."); + } + + /** + * Test with multiple wines of the same price. + */ + @Test + void testSamePriceWines() { + int[] wines = {5, 5, 5}; // All wines have the same price + int expectedProfit = 30; // Profit is 5 * (1 + 2 + 3) + assertEquals(expectedProfit, WineProblem.wpbu(wines), + "The maximum profit with same price wines should be 30."); + } + + /** + * Test with no wines. + */ + @Test + void testNoWines() { + int[] wines = {}; // No wines + assertThrows(IllegalArgumentException.class, () -> WineProblem.wpbu(wines), + "The maximum profit for no wines should throw an IllegalArgumentException."); + } +} From 2e85f0e940b8407cb357e45af8d61055a83cd49b Mon Sep 17 00:00:00 2001 From: Hardvan Date: Wed, 9 Oct 2024 06:01:07 +0000 Subject: [PATCH 2/4] Update directory --- DIRECTORY.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/DIRECTORY.md b/DIRECTORY.md index 6042dd1b5e0d..80f339f323ea 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -31,6 +31,7 @@ * [IsPowerTwo](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/IsPowerTwo.java) * [LowestSetBit](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/LowestSetBit.java) * [NonRepeatingNumberFinder](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/NonRepeatingNumberFinder.java) + * [NumberAppearingOddTimes](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/NumberAppearingOddTimes.java) * [NumbersDifferentSigns](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/NumbersDifferentSigns.java) * [ReverseBits](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/ReverseBits.java) * [SingleBitOperations](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/SingleBitOperations.java) @@ -638,6 +639,7 @@ * [IsPowerTwoTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/IsPowerTwoTest.java) * [LowestSetBitTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/LowestSetBitTest.java) * [NonRepeatingNumberFinderTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/NonRepeatingNumberFinderTest.java) + * [NumberAppearingOddTimesTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/NumberAppearingOddTimesTest.java) * [NumbersDifferentSignsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/NumbersDifferentSignsTest.java) * [ReverseBitsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/ReverseBitsTest.java) * [SingleBitOperationsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/SingleBitOperationsTest.java) @@ -804,6 +806,7 @@ * [UniquePathsTests](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/UniquePathsTests.java) * [UniqueSubsequencesCountTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/UniqueSubsequencesCountTest.java) * [WildcardMatchingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/WildcardMatchingTest.java) + * [WineProblemTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/WineProblemTest.java) * geometry * [GrahamScanTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/geometry/GrahamScanTest.java) * greedyalgorithms From 3e1a889f36d14e5682395adfe1e1eb574f4ddf86 Mon Sep 17 00:00:00 2001 From: Hardik Pawar Date: Wed, 9 Oct 2024 11:32:16 +0530 Subject: [PATCH 3/4] Fix --- .../com/thealgorithms/dynamicprogramming/WineProblemTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/com/thealgorithms/dynamicprogramming/WineProblemTest.java b/src/test/java/com/thealgorithms/dynamicprogramming/WineProblemTest.java index ddde9203b60c..a83b838b9a94 100644 --- a/src/test/java/com/thealgorithms/dynamicprogramming/WineProblemTest.java +++ b/src/test/java/com/thealgorithms/dynamicprogramming/WineProblemTest.java @@ -71,7 +71,7 @@ void testSamePriceWines() { */ @Test void testNoWines() { - int[] wines = {}; // No wines + int[] wines = {}; assertThrows(IllegalArgumentException.class, () -> WineProblem.wpbu(wines), "The maximum profit for no wines should throw an IllegalArgumentException."); } From 167d13c17e19dcdeedae4ead6e0114f360c7e230 Mon Sep 17 00:00:00 2001 From: Hardik Pawar Date: Wed, 9 Oct 2024 11:36:55 +0530 Subject: [PATCH 4/4] Fix --- .../dynamicprogramming/WineProblemTest.java | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/test/java/com/thealgorithms/dynamicprogramming/WineProblemTest.java b/src/test/java/com/thealgorithms/dynamicprogramming/WineProblemTest.java index a83b838b9a94..fbcc2c6f3a83 100644 --- a/src/test/java/com/thealgorithms/dynamicprogramming/WineProblemTest.java +++ b/src/test/java/com/thealgorithms/dynamicprogramming/WineProblemTest.java @@ -18,8 +18,7 @@ class WineProblemTest { void testWpRecursion() { int[] wines = {2, 3, 5, 1, 4}; // Prices of wines int expectedProfit = 50; // The expected maximum profit - assertEquals(expectedProfit, WineProblem.wpRecursion(wines, 0, wines.length - 1), - "The maximum profit using recursion should be 50."); + assertEquals(expectedProfit, WineProblem.wpRecursion(wines, 0, wines.length - 1), "The maximum profit using recursion should be 50."); } /** @@ -29,8 +28,7 @@ void testWpRecursion() { void testWptd() { int[] wines = {2, 3, 5, 1, 4}; // Prices of wines int expectedProfit = 50; // The expected maximum profit - assertEquals(expectedProfit, WineProblem.wptd(wines, 0, wines.length - 1, new int[wines.length][wines.length]), - "The maximum profit using top-down DP should be 50."); + assertEquals(expectedProfit, WineProblem.wptd(wines, 0, wines.length - 1, new int[wines.length][wines.length]), "The maximum profit using top-down DP should be 50."); } /** @@ -40,8 +38,7 @@ void testWptd() { void testWpbu() { int[] wines = {2, 3, 5, 1, 4}; // Prices of wines int expectedProfit = 50; // The expected maximum profit - assertEquals(expectedProfit, WineProblem.wpbu(wines), - "The maximum profit using bottom-up DP should be 50."); + assertEquals(expectedProfit, WineProblem.wpbu(wines), "The maximum profit using bottom-up DP should be 50."); } /** @@ -51,8 +48,7 @@ void testWpbu() { void testSingleWine() { int[] wines = {10}; // Only one wine int expectedProfit = 10; // Selling the only wine at year 1 - assertEquals(expectedProfit, WineProblem.wpbu(wines), - "The maximum profit for a single wine should be 10."); + assertEquals(expectedProfit, WineProblem.wpbu(wines), "The maximum profit for a single wine should be 10."); } /** @@ -62,8 +58,7 @@ void testSingleWine() { void testSamePriceWines() { int[] wines = {5, 5, 5}; // All wines have the same price int expectedProfit = 30; // Profit is 5 * (1 + 2 + 3) - assertEquals(expectedProfit, WineProblem.wpbu(wines), - "The maximum profit with same price wines should be 30."); + assertEquals(expectedProfit, WineProblem.wpbu(wines), "The maximum profit with same price wines should be 30."); } /** @@ -72,7 +67,6 @@ void testSamePriceWines() { @Test void testNoWines() { int[] wines = {}; - assertThrows(IllegalArgumentException.class, () -> WineProblem.wpbu(wines), - "The maximum profit for no wines should throw an IllegalArgumentException."); + assertThrows(IllegalArgumentException.class, () -> WineProblem.wpbu(wines), "The maximum profit for no wines should throw an IllegalArgumentException."); } }