As a kind of recursion, you can use Stream.reduce
method. First prepare a list of possible combinations of characters for each character-position, and then consecutively reduce the stream of these lists to a single list, by summing the pairs of list elements.
As a list element, you can use Map<Integer,String>
, where key - the position of the character in the string, value - the character itself, and summ the contents of these maps, excluding those keys that are already present.
And you get a list of permutations of characters.
For example, if you have a four-character string
, then you have to pass through three steps of reduction, consecutively accumulating the results:
str1 + str2 = sum1 |
sum1 + str3 = sum2 |
sum2 + str4 = total |
+ = + = + = |
+ = + = + = + = + = + = |
+ = + = + = + = + = + = |
15
sums for each of the 4
characters are 60
sums, resulting in 4! = 24
permutations.
Try it online!
// original string
String str = "";
// array of characters of the string
int[] codePoints = str.codePoints().toArray();
// contents of the array
System.out.println(Arrays.toString(codePoints));
//[120276, 120277, 120278, 120279]
// list of permutations of characters
List<Map<Integer, String>> permutations = IntStream.range(0, codePoints.length)
// Stream<List<Map<Integer,String>>>
.mapToObj(i -> IntStream.range(0, codePoints.length)
// represent each character as Map<Integer,String>
.mapToObj(j -> Map.of(j, Character.toString(codePoints[j])))
// collect a list of maps
.collect(Collectors.toList()))
// intermediate output
//[{0=}, {1=}, {2=}, {3=}]
//[{0=}, {1=}, {2=}, {3=}]
//[{0=}, {1=}, {2=}, {3=}]
//[{0=}, {1=}, {2=}, {3=}]
.peek(System.out::println)
// reduce a stream of lists to a single list
.reduce((list1, list2) -> list1.stream()
// summation of pairs of maps from two lists
.flatMap(map1 -> list2.stream()
// filter out those keys that are already present
.filter(map2 -> map2.keySet().stream()
.noneMatch(map1::containsKey))
// join entries of two maps
.map(map2 -> new LinkedHashMap<Integer, String>() {{
putAll(map1);
putAll(map2);
}}))
// collect into a single list
.collect(Collectors.toList()))
// List<Map<Integer,String>>
.orElse(List.of(Map.of(0, str)));
// number of permutations
System.out.println(permutations.size()); // 24
// number of rows (factorial of string length - 1)
int rows = IntStream.range(1, codePoints.length)
.reduce((a, b) -> a * b).orElse(1);
// column-wise output
IntStream.range(0, rows)
.mapToObj(i -> IntStream.range(0, permutations.size())
.filter(j -> j % rows == i)
.mapToObj(permutations::get)
.map(map -> map.toString().replace(" ", ""))
.collect(Collectors.joining(" ")))
.forEach(System.out::println);
//{0=,1=,2=,3=} {1=,0=,2=,3=} {2=,0=,1=,3=} {3=,0=,1=,2=}
//{0=,1=,3=,2=} {1=,0=,3=,2=} {2=,0=,3=,1=} {3=,0=,2=,1=}
//{0=,2=,1=,3=} {1=,2=,0=,3=} {2=,1=,0=,3=} {3=,1=,0=,2=}
//{0=,2=,3=,1=} {1=,2=,3=,0=} {2=,1=,3=,0=} {3=,1=,2=,0=}
//{0=,3=,1=,2=} {1=,3=,0=,2=} {2=,3=,0=,1=} {3=,2=,0=,1=}
//{0=,3=,2=,1=} {1=,3=,2=,0=} {2=,3=,1=,0=} {3=,2=,1=,0=}
See also: How do you check if a word has an anagram that is a palindrome?