-
Notifications
You must be signed in to change notification settings - Fork 21.2k
Created a Permutation Function in the Recursion package. Closed the issue #7322 #7412
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
9509e4d
fdf5fd1
0c53a72
a5b3864
d9e43ba
c1368b4
bc0ebaf
a8a8e13
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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. | ||
| * <p> | ||
| * 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 <T> 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 <T> List<List<T>> permutations(T[] items) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As mentioned by @DenizAltunkapan, a Permutation class already |
||
| 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<List<T>> result = new ArrayList<>(); | ||
| List<T> list = new ArrayList<>(Arrays.asList(items)); | ||
| generatePermutations(0, list, result); | ||
| return result; | ||
| } | ||
|
|
||
| /** | ||
| * Recursive backtracking to generate permutations. | ||
| * | ||
| * @param <T> 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 <T> void generatePermutations(int index, List<T> list, List<List<T>> result) { | ||
| if (index == list.size()) { | ||
| result.add(new ArrayList<>(list)); | ||
| return; | ||
| } | ||
|
|
||
| Set<T> seen = new HashSet<>(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. HashSet relies on |
||
| 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 <T> the type of elements | ||
| * @param list the list | ||
| * @param i first index | ||
| * @param j second index | ||
| */ | ||
| private static <T> void swap(List<T> list, int i, int j) { | ||
| T temp = list.get(i); | ||
| list.set(i, list.get(j)); | ||
| list.set(j, temp); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Test file name should be |
||
|
|
||
| // ───────────────────────────────────────────── | ||
| // 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<List<Integer>> result = Permutations.permutations(items); | ||
| assertEquals(1, result.size()); | ||
| assertTrue(result.get(0).isEmpty()); | ||
| } | ||
|
|
||
| @Test | ||
| void testSingleElementReturnsOnePermutation() { | ||
| Integer[] items = {42}; | ||
| List<List<Integer>> 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<List<Integer>> 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<List<Integer>> result = Permutations.permutations(items); | ||
| assertEquals(6, result.size()); | ||
| } | ||
|
|
||
| @Test | ||
| void testIntegerPermutationsContainAllExpectedOrders() { | ||
| Integer[] items = {1, 2, 3}; | ||
| List<List<Integer>> 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<List<Integer>> 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<List<Integer>> 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<List<Integer>> 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<List<String>> 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<List<String>> result = Permutations.permutations(items); | ||
| assertEquals(6, result.size()); | ||
| } | ||
|
|
||
| @Test | ||
| void testDuplicateStringsReturnsCorrectCount() { | ||
| String[] items = {"a", "a", "b"}; | ||
| List<List<String>> 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<List<Character>> result = Permutations.permutations(items); | ||
| assertEquals(6, result.size()); | ||
| } | ||
|
|
||
| @Test | ||
| void testDuplicateCharactersReturnsCorrectCount() { | ||
| Character[] items = {'a', 'a', 'b'}; | ||
| List<List<Character>> result = Permutations.permutations(items); | ||
| assertEquals(3, result.size()); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Private constructor is not tested — this is causing
2 uncovered lines in Codecov.
Add:
@test
void testConstructorThrowsException() {
assertThrows(UnsupportedOperationException.class, () -> {
var constructor = Permutations.class.getDeclaredConstructor();
constructor.setAccessible(true);
constructor.newInstance();
});
}