概述
排列是集合中元素的重新排列。换句话说,它是集合所有可能的顺序变化。
在这个教程中,我们将学习如何使用第三方库轻松在Java中生成排列。特别是,我们将关注字符串中的排列。
2. 排列
有时,我们需要检查一个字符串值的所有可能排列,这通常出现在令人费解的在线编程练习中,而在日常工作中较少遇到。例如,字符串"abc"有六种不同的方式来排列内部字符:"abc"、"acb"、"cab"、"bac"、"bca"和"cba"。
有几个定义明确的算法可以帮助我们为特定的String
值创建所有可能的排列。例如,最著名的可能是Heap算法。然而,它相当复杂且不易理解。再加上递归方法,情况会变得更糟。
3. 优雅解决方案
为生成排列编写算法需要编写自定义逻辑。实现过程中容易出错,并且很难长期确保其正确性。此外,没有必要重写已经写过的东西。
另外,在处理String
值时,如果不谨慎,可能会因创建太多实例而填满String
池。
以下是当前提供此类功能的库:
- Apache Commons
- Guava
- CombinatoricsLib
让我们尝试使用这些库找出一个String
值的所有排列。我们将关注这些库是否支持懒惰遍历排列,并处理输入值中的重复项。
下面的例子将使用Helper.toCharacterList
方法。这个方法封装了将String
转换为Character
列表的复杂性:
static List<Character> toCharacterList(final String string) {
return string.chars().mapToObj(s -> ((char) s)).collect(Collectors.toList());
}
同时,我们还将使用一个辅助方法将Character
列表转换为String
:
static String toString(Collection<Character> collection) {
return collection.stream().map(s -> s.toString()).collect(Collectors.joining());
}
4. Apache Commons
首先,将Maven依赖commons-collections4
添加到项目中:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.4</version>
</dependency>
总体来说,Apache提供了简单的API。CollectionUtils
迫切地创建排列,因此在处理长String
值时要小心:
public List<String> eagerPermutationWithRepetitions(final String string) {
final List<Character> characters = Helper.toCharacterList(string);
return CollectionUtils.permutations(characters)
.stream()
.map(Helper::toString)
.collect(Collectors.toList());
}
然而,为了采用懒惰的方法,我们应该使用PermutationIterator
:
public List<String> lazyPermutationWithoutRepetitions(final String string) {
final List<Character> characters = Helper.toCharacterList(string);
final PermutationIterator<Character> permutationIterator = new PermutationIterator<>(characters);
final List<String> result = new ArrayList<>();
while (permutationIterator.hasNext()) {
result.add(Helper.toString(permutationIterator.next()));
}
return result;
}
此库不处理重复项,所以输入字符串"aaaaaa"会产生720个排列,这通常是不希望看到的。此外,PermutationIterator
没有获取排列数量的方法。在这种情况下,我们需要根据输入大小单独计算它们。
5. Guava
首先,为Guava库添加Maven依赖:
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>33.0.0-jre</version>
</dependency>
Guava允许使用Collections2
创建排列。API使用起来很简单:
public List<String> permutationWithRepetitions(final String string) {
final List<Character> characters = Helper.toCharacterList(string);
return Collections2.permutations(characters).stream()
.map(Helper::toString)
.collect(Collectors.toList());
}
Collections2.permutations
的结果是一个PermutationCollection
,可以方便地访问排列。所有排列都是懒惰创建的。
此外,这个类还提供了创建无重复排列的API:
public List<String> permutationWithoutRepetitions(final String string) {
final List<Character> characters = Helper.toCharacterList(string);
return Collections2.orderedPermutations(characters).stream()
.map(Helper::toString)
.collect(Collectors.toList());
}
然而,这些方法的问题是它们被标记为@Beta
注解,这意味着这个API在未来版本中可能会更改。
6. CombinatoricsLib
要在项目中使用它,请添加Maven依赖combinatoricslib3:
<dependency>
<groupId>com.github.dpaukov</groupId>
<artifactId>combinatoricslib3</artifactId>
<version>3.3.3</version>
</dependency>
尽管这是一个小库,但它提供了许多组合学工具,包括排列。API本身非常直观,并利用Java流。让我们从特定的String
或Character
列表创建排列:
public List<String> permutationWithoutRepetitions(final String string) {
List<Character> chars = Helper.toCharacterList(string);
return Generator.permutation(chars)
.simple()
.stream()
.map(Helper::toString)
.collect(Collectors.toList());
}
上述代码创建了一个生成器,它将为字符串提供排列。排列将被懒惰地获取。因此,我们只创建了一个生成器并计算了预期的排列数。
同时,使用此库,我们可以确定处理重复项的策略。以字符串"aaaaaa"为例,我们将得到一个排列,而不是720个相同的排列。
public List<String> permutationWithRepetitions(final String string) {
List<Character> chars = Helper.toCharacterList(string);
return Generator.permutation(chars)
.simple(TreatDuplicatesAs.IDENTICAL)
.stream()
.map(Helper::toString)
.collect(Collectors.toList());
}
TreatDuplicatesAs
允许我们定义如何处理重复项。
7. 总结
处理组合学,尤其是排列,有很多方法。这些库都能在很大程度上帮助我们。值得尝试所有选项,然后决定哪个最适合你的需求。尽管有时人们被鼓励自己编写所有代码,但没有理由在已经有良好功能的地方浪费时间。
如往常一样,示例代码可在GitHub上的源代码仓库中找到。