diff --git a/src/main/java/com/thealgorithms/recursion/Permutations.java b/src/main/java/com/thealgorithms/recursion/Permutations.java new file mode 100644 index 000000000000..5550a0dba5fd --- /dev/null +++ b/src/main/java/com/thealgorithms/recursion/Permutations.java @@ -0,0 +1,84 @@ +package com.thealgorithms.recursion; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * This class provides methods to generate all permutations + * of a given array of any type using recursion. + *

+ * Reference: + * https://en.wikipedia.org/wiki/Permutation + */ +public final class Permutations { + + private Permutations() { + throw new UnsupportedOperationException("Utility class"); + } + + /** + * Generates all permutations of a generic array. + * + * @param the type of elements in the array + * @param items the input array + * @return list of all permutations + * @throws NullPointerException if items is null + * @throws IllegalArgumentException if any element in items is null + */ + public static List> permutations(T[] items) { + if (items == null) { + throw new NullPointerException("Input array cannot be null"); + } + for (T item : items) { + if (item == null) { + throw new IllegalArgumentException("Array elements cannot be null"); + } + } + + List> result = new ArrayList<>(); + List list = new ArrayList<>(Arrays.asList(items)); + generatePermutations(0, list, result); + return result; + } + + /** + * Recursive backtracking to generate permutations. + * + * @param the type of elements + * @param index the current position being fixed + * @param list the working list of elements + * @param result the accumulated list of permutations + */ + private static void generatePermutations(int index, List list, List> result) { + if (index == list.size()) { + result.add(new ArrayList<>(list)); + return; + } + + Set seen = new HashSet<>(); + for (int i = index; i < list.size(); i++) { + if (seen.add(list.get(i))) { // skip duplicate values at this position + swap(list, index, i); + generatePermutations(index + 1, list, result); + swap(list, index, i); // backtrack + } + } + } + + /** + * Swaps two elements in a list. + * + * @param the type of elements + * @param list the list + * @param i first index + * @param j second index + */ + private static void swap(List list, int i, int j) { + T temp = list.get(i); + list.set(i, list.get(j)); + list.set(j, temp); + } +} diff --git a/src/test/java/com/thealgorithms/recursion/Permutations.java b/src/test/java/com/thealgorithms/recursion/Permutations.java new file mode 100644 index 000000000000..e548ee505daf --- /dev/null +++ b/src/test/java/com/thealgorithms/recursion/Permutations.java @@ -0,0 +1,157 @@ +package com.thealgorithms.recursion; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import org.junit.jupiter.api.Test; + +class PermutationsTest { + + // ───────────────────────────────────────────── + // Null / Invalid Input Tests + // ───────────────────────────────────────────── + + @Test + void testNullArrayThrowsNullPointerException() { + assertThrows(NullPointerException.class, () -> Permutations.permutations((Integer[]) null)); + } + + @Test + void testArrayWithNullElementThrowsIllegalArgumentException() { + Integer[] items = {1, null, 3}; + assertThrows(IllegalArgumentException.class, () -> Permutations.permutations(items)); + } + + // ───────────────────────────────────────────── + // Edge Case Tests + // ───────────────────────────────────────────── + + @Test + void testEmptyArrayReturnsOneEmptyPermutation() { + Integer[] items = {}; + List> result = Permutations.permutations(items); + assertEquals(1, result.size()); + assertTrue(result.get(0).isEmpty()); + } + + @Test + void testSingleElementReturnsOnePermutation() { + Integer[] items = {42}; + List> result = Permutations.permutations(items); + assertEquals(1, result.size()); + assertEquals(List.of(42), result.get(0)); + } + + // ───────────────────────────────────────────── + // Integer Permutation Tests + // ───────────────────────────────────────────── + + @Test + void testTwoIntegersReturnsTwoPermutations() { + Integer[] items = {1, 2}; + List> result = Permutations.permutations(items); + assertEquals(2, result.size()); + assertTrue(result.contains(List.of(1, 2))); + assertTrue(result.contains(List.of(2, 1))); + } + + @Test + void testThreeIntegersReturnsSixPermutations() { + Integer[] items = {1, 2, 3}; + List> result = Permutations.permutations(items); + assertEquals(6, result.size()); + } + + @Test + void testIntegerPermutationsContainAllExpectedOrders() { + Integer[] items = {1, 2, 3}; + List> result = Permutations.permutations(items); + assertTrue(result.contains(List.of(1, 2, 3))); + assertTrue(result.contains(List.of(1, 3, 2))); + assertTrue(result.contains(List.of(2, 1, 3))); + assertTrue(result.contains(List.of(2, 3, 1))); + assertTrue(result.contains(List.of(3, 1, 2))); + assertTrue(result.contains(List.of(3, 2, 1))); + } + + // ───────────────────────────────────────────── + // Duplicate Handling Tests + // ───────────────────────────────────────────── + + @Test + void testTwoDuplicateIntegersReturnsOnePermutation() { + Integer[] items = {1, 1}; + List> result = Permutations.permutations(items); + assertEquals(1, result.size()); + assertEquals(List.of(1, 1), result.get(0)); + } + + @Test + void testArrayWithDuplicatesReturnsCorrectCount() { + Integer[] items = {1, 1, 2}; + List> result = Permutations.permutations(items); + // 3!/2! = 3 unique permutations + assertEquals(3, result.size()); + assertTrue(result.contains(List.of(1, 1, 2))); + assertTrue(result.contains(List.of(1, 2, 1))); + assertTrue(result.contains(List.of(2, 1, 1))); + } + + @Test + void testAllDuplicatesReturnsOnePermutation() { + Integer[] items = {5, 5, 5}; + List> result = Permutations.permutations(items); + assertEquals(1, result.size()); + assertEquals(List.of(5, 5, 5), result.get(0)); + } + + // ───────────────────────────────────────────── + // String Permutation Tests + // ───────────────────────────────────────────── + + @Test + void testTwoStringsReturnsTwoPermutations() { + String[] items = {"a", "b"}; + List> result = Permutations.permutations(items); + assertEquals(2, result.size()); + assertTrue(result.contains(List.of("a", "b"))); + assertTrue(result.contains(List.of("b", "a"))); + } + + @Test + void testThreeStringsReturnsSixPermutations() { + String[] items = {"x", "y", "z"}; + List> result = Permutations.permutations(items); + assertEquals(6, result.size()); + } + + @Test + void testDuplicateStringsReturnsCorrectCount() { + String[] items = {"a", "a", "b"}; + List> result = Permutations.permutations(items); + assertEquals(3, result.size()); + assertTrue(result.contains(List.of("a", "a", "b"))); + assertTrue(result.contains(List.of("a", "b", "a"))); + assertTrue(result.contains(List.of("b", "a", "a"))); + } + + // ───────────────────────────────────────────── + // Character Permutation Tests + // ───────────────────────────────────────────── + + @Test + void testCharacterPermutations() { + Character[] items = {'a', 'b', 'c'}; + List> result = Permutations.permutations(items); + assertEquals(6, result.size()); + } + + @Test + void testDuplicateCharactersReturnsCorrectCount() { + Character[] items = {'a', 'a', 'b'}; + List> result = Permutations.permutations(items); + assertEquals(3, result.size()); + } +}