diff --git a/hutool-core/src/main/java/cn/hutool/core/math/Arrangement.java b/hutool-core/src/main/java/cn/hutool/core/math/Arrangement.java
index d04edc82d4..1ade5a3252 100644
--- a/hutool-core/src/main/java/cn/hutool/core/math/Arrangement.java
+++ b/hutool-core/src/main/java/cn/hutool/core/math/Arrangement.java
@@ -1,12 +1,9 @@
package cn.hutool.core.math;
-import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
import cn.hutool.core.util.ArrayUtil;
-import cn.hutool.core.util.NumberUtil;
+
+import java.io.Serializable;
+import java.util.*;
/**
* 排列A(n, m)
@@ -47,10 +44,23 @@ public class Arrangement implements Serializable {
* @return 排列数
*/
public static long count(int n, int m) {
- if (n == m) {
- return NumberUtil.factorial(n);
+ if (m < 0 || m > n) {
+ throw new IllegalArgumentException("n >= 0 && m >= 0 && m <= n required");
}
- return (n > m) ? NumberUtil.factorial(n, n - m) : 0;
+ if (m == 0) {
+ return 1;
+ }
+ long result = 1;
+ // 从 n 到 n-m+1 逐个乘
+ for (int i = 0; i < m; i++) {
+ long next = result * (n - i);
+ // 溢出检测
+ if (next < result) {
+ throw new ArithmeticException("Overflow computing A(" + n + "," + m + ")");
+ }
+ result = next;
+ }
+ return result;
}
/**
@@ -77,26 +87,85 @@ public class Arrangement implements Serializable {
}
/**
- * 排列选择(从列表中选择m个排列)
+ * 从当前数据中选择 m 个元素,生成所有「不重复」的排列(Permutation)。
*
- * @param m 选择个数
- * @return 所有排列列表
+ *
+ * 说明:
+ *
+ * - 不允许重复选择同一个元素(即经典排列 A(n, m))
+ * - 结果中不会出现 ["1","1"] 这种重复元素的情况
+ * - 顺序敏感,因此 ["1","2"] 与 ["2","1"] 都会包含
+ *
+ *
+ * 数量公式:
+ *
+ * A(n, m) = n! / (n - m)!
+ *
+ *
+ * 举例:
+ *
+ * datas = ["1","2","3"]
+ * m = 2
+ * 输出:
+ * ["1","2"]
+ * ["1","3"]
+ * ["2","1"]
+ * ["2","3"]
+ * ["3","1"]
+ * ["3","2"]
+ * 共 6 个(A(3,2)=6)
+ *
+ *
+ * @param m 选择的元素个数
+ * @return 所有长度为 m 的不重复排列列表
*/
+
public List select(int m) {
- final List result = new ArrayList<>((int) count(this.datas.length, m));
- select(this.datas, new String[m], 0, result);
+ if (m < 0 || m > datas.length) {
+ return Collections.emptyList();
+ }
+ if (m == 0) {
+ // A(n,0) = 1,唯一一个空排列
+ return Collections.singletonList(new String[0]);
+ }
+
+ long estimated = count(datas.length, m);
+ int capacity = estimated > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) estimated;
+
+ List result = new ArrayList<>(capacity);
+ boolean[] visited = new boolean[datas.length];
+ dfs(new String[m], 0, visited, result);
return result;
}
+
/**
- * 排列所有组合,即A(n, 1) + A(n, 2) + A(n, 3)...
+ * 生成当前数据的全部不重复排列(长度为 1 至 n 的所有排列)。
*
- * @return 全排列结果
+ *
+ * 说明:
+ *
+ * - 不允许重复选择元素(无 ["1","1"],无 ["2","2","3"] 这种)
+ * - 包含所有长度 m=1..n 的排列
+ * - 总数量为 A(n,1) + A(n,2) + ... + A(n,n)
+ *
+ *
+ * 举例(datas = ["1","2","3"]):
+ *
+ * m=1: ["1"], ["2"], ["3"] → 3 个
+ * m=2: ["1","2"], ["1","3"], ["2","1"], ... → 6 个
+ * m=3: ["1","2","3"], ["1","3","2"], ["2","1","3"], ...→ 6 个
+ *
+ * 总共:3 + 6 + 6 = 15
+ *
+ *
+ * @return 所有不重复排列列表
*/
+
public List selectAll() {
- final List result = new ArrayList<>((int) countAll(this.datas.length));
- for (int i = 1; i <= this.datas.length; i++) {
- result.addAll(select(i));
+ List result = new ArrayList<>();
+ for (int m = 1; m <= datas.length; m++) {
+ result.addAll(select(m));
}
return result;
}
@@ -124,4 +193,113 @@ public class Arrangement implements Serializable {
select(ArrayUtil.remove(datas, i), resultList, resultIndex + 1, result);
}
}
+
+ /**
+ * 返回一个排列的迭代器
+ *
+ * @param m 选择的元素个数
+ * @return 排列迭代器
+ */
+ public Iterable iterate(int m) {
+ return () -> new ArrangementIterator(datas, m);
+ }
+
+
+ /**
+ * 排列迭代器
+ *
+ * @author CherryRum
+ */
+ private static class ArrangementIterator implements Iterator {
+
+ private final String[] datas;
+ private final int m;
+ private final boolean[] visited;
+ private final String[] buffer;
+ private final Deque stack = new ArrayDeque<>();
+ boolean end = false;
+
+ ArrangementIterator(String[] datas, int m) {
+ this.datas = datas;
+ this.m = m;
+ this.visited = new boolean[datas.length];
+ this.buffer = new String[m];
+ // 初始化 dfs 栈
+ stack.push(0);
+ }
+
+ @Override
+ public boolean hasNext() {
+ return !end;
+ }
+
+ @Override
+ public String[] next() {
+ while (!stack.isEmpty()) {
+ int depth = stack.size() - 1;
+
+ int idx = stack.pop();
+ if (idx >= datas.length) {
+ // 这一层遍历结束
+ if (!stack.isEmpty()) {
+ int prev = stack.pop();
+ stack.push(prev + 1);
+ }
+ continue;
+ }
+
+ // 如果该元素未使用
+ if (!visited[idx]) {
+ visited[idx] = true;
+ buffer[depth] = datas[idx];
+
+ if (depth == m - 1) {
+ // 输出一个排列
+ visited[idx] = false;
+
+ // 下一次从 idx+1 继续
+ stack.push(idx + 1);
+
+ return Arrays.copyOf(buffer, m);
+ } else {
+ // 继续下一层
+ stack.push(idx + 1); // 当前层下一个起点
+ stack.push(0); // 下一层起点
+ continue;
+ }
+ }
+
+ // 已访问则跳过
+ stack.push(idx + 1);
+ }
+
+ end = true;
+ return null;
+ }
+ }
+
+ /**
+ * 核心递归方法(回溯算法)
+ * * @param current 当前构建的排列数组
+ *
+ * @param depth 当前递归深度(填到了第几个位置)
+ * @param visited 标记数组,记录哪些索引已经被使用了
+ * @param result 结果集
+ */
+ private void dfs(String[] current, int depth, boolean[] visited, List result) {
+ if (depth == current.length) {
+ result.add(Arrays.copyOf(current, current.length));
+ return;
+ }
+
+ for (int i = 0; i < datas.length; i++) {
+ if (!visited[i]) {
+ visited[i] = true;
+ current[depth] = datas[i];
+
+ dfs(current, depth + 1, visited, result);
+ visited[i] = false;
+ }
+ }
+ }
}
diff --git a/hutool-core/src/test/java/cn/hutool/core/math/ArrangementTest.java b/hutool-core/src/test/java/cn/hutool/core/math/ArrangementTest.java
index 9819613d3c..b160d7fa29 100755
--- a/hutool-core/src/test/java/cn/hutool/core/math/ArrangementTest.java
+++ b/hutool-core/src/test/java/cn/hutool/core/math/ArrangementTest.java
@@ -1,20 +1,22 @@
package cn.hutool.core.math;
+import cn.hutool.core.lang.Console;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
-import org.junit.jupiter.api.Disabled;
-import org.junit.jupiter.api.Test;
-
-import cn.hutool.core.lang.Console;
/**
* 排列单元测试
* @author looly
- *
*/
public class ArrangementTest {
+ // ----------------------------------------------------
+ // 基础测试
+ // ----------------------------------------------------
@Test
public void arrangementTest() {
long result = Arrangement.count(4, 2);
@@ -30,37 +32,117 @@ public class ArrangementTest {
assertEquals(64, resultAll);
}
+ // ----------------------------------------------------
+ // select 基础测试
+ // ----------------------------------------------------
@Test
public void selectTest() {
- Arrangement arrangement = new Arrangement(new String[] { "1", "2", "3", "4" });
+ Arrangement arrangement = new Arrangement(new String[]{"1", "2", "3", "4"});
List list = arrangement.select(2);
- assertEquals(Arrangement.count(4, 2), list.size());
- assertArrayEquals(new String[] {"1", "2"}, list.get(0));
- assertArrayEquals(new String[] {"1", "3"}, list.get(1));
- assertArrayEquals(new String[] {"1", "4"}, list.get(2));
- assertArrayEquals(new String[] {"2", "1"}, list.get(3));
- assertArrayEquals(new String[] {"2", "3"}, list.get(4));
- assertArrayEquals(new String[] {"2", "4"}, list.get(5));
- assertArrayEquals(new String[] {"3", "1"}, list.get(6));
- assertArrayEquals(new String[] {"3", "2"}, list.get(7));
- assertArrayEquals(new String[] {"3", "4"}, list.get(8));
- assertArrayEquals(new String[] {"4", "1"}, list.get(9));
- assertArrayEquals(new String[] {"4", "2"}, list.get(10));
- assertArrayEquals(new String[] {"4", "3"}, list.get(11));
+ // 校验数量一致
+ assertEquals(Arrangement.count(4, 2), list.size());
+
+ // 逐项严格校验顺序是否一致(按 DFS 顺序)
+ assertArrayEquals(new String[]{"1", "2"}, list.get(0));
+ assertArrayEquals(new String[]{"1", "3"}, list.get(1));
+ assertArrayEquals(new String[]{"1", "4"}, list.get(2));
+ assertArrayEquals(new String[]{"2", "1"}, list.get(3));
+ assertArrayEquals(new String[]{"2", "3"}, list.get(4));
+ assertArrayEquals(new String[]{"2", "4"}, list.get(5));
+ assertArrayEquals(new String[]{"3", "1"}, list.get(6));
+ assertArrayEquals(new String[]{"3", "2"}, list.get(7));
+ assertArrayEquals(new String[]{"3", "4"}, list.get(8));
+ assertArrayEquals(new String[]{"4", "1"}, list.get(9));
+ assertArrayEquals(new String[]{"4", "2"}, list.get(10));
+ assertArrayEquals(new String[]{"4", "3"}, list.get(11));
+
+ // 测试 selectAll
List selectAll = arrangement.selectAll();
assertEquals(Arrangement.countAll(4), selectAll.size());
+ // m=0,应该返回一个空排列
List list2 = arrangement.select(0);
assertEquals(1, list2.size());
+ assertEquals(0, list2.get(0).length);
}
+ // ----------------------------------------------------
+ // 扩展测试:边界、错误处理
+ // ----------------------------------------------------
@Test
- @Disabled
- public void selectTest2() {
- List list = MathUtil.arrangementSelect(new String[] { "1", "1", "3", "4" });
- for (String[] strings : list) {
- Console.log(strings);
+ public void boundaryTest() {
+ Arrangement arr = new Arrangement(new String[]{"A", "B", "C"});
+
+ // m = n
+ List full = arr.select(3);
+ assertEquals(6, full.size());
+
+ // m = 1
+ List one = arr.select(1);
+ assertEquals(3, one.size());
+ assertArrayEquals(new String[]{"A"}, one.get(0));
+
+ // m > n → empty list
+ assertTrue(arr.select(10).isEmpty());
+
+ // m < 0 → empty list
+ assertTrue(arr.select(-1).isEmpty());
+ }
+
+ // ----------------------------------------------------
+ // 扩展测试:空数组
+ // ----------------------------------------------------
+ @Test
+ public void emptyTest() {
+ Arrangement arrangement = new Arrangement(new String[]{});
+
+ assertEquals(1, arrangement.select(0).size());
+ assertTrue(arrangement.select(1).isEmpty());
+ assertTrue(arrangement.selectAll().isEmpty()); // A(0,m) = 0 for m>0,A(0,0)=1 → 全排列 = 1 个空排列
+ }
+
+ // ----------------------------------------------------
+ // 扩展测试:重复元素(用于验证去重算法)
+ // 默认 Arrangement 不去重,因此应该包含重复排列
+ // ----------------------------------------------------
+ @Test
+ @Disabled("默认 Arrangement 不支持去重;启用后手动检查")
+ public void duplicateElementTest() {
+ Arrangement arrangement = new Arrangement(new String[]{"1", "1", "3"});
+
+ List list = arrangement.select(2);
+
+ // 应该有 A(3,2) = 6 个
+ assertEquals(6, list.size());
+
+ for (String[] s : list) {
+ Console.log(s);
}
}
+
+ // ----------------------------------------------------
+ // 扩展测试:selectAll 覆盖全部不重复排列(A(n,1..n))
+ // ----------------------------------------------------
+ @Test
+ public void selectAllTest() {
+ Arrangement arrangement = new Arrangement(new String[]{"1", "2", "3"});
+
+ List all = arrangement.selectAll();
+
+ // 打印用于观测
+ for (String[] s : all) {
+ Console.log(s);
+ }
+
+ // A(3,1) + A(3,2) + A(3,3) = 3 + 6 + 6 = 15
+ assertEquals(Arrangement.countAll(3), all.size());
+ assertEquals(15, all.size());
+
+ // spot check 不重复排列
+ assertArrayEquals(new String[]{"1"}, all.get(0));
+ assertArrayEquals(new String[]{"1", "2"}, all.get(3));
+ assertArrayEquals(new String[]{"1", "2", "3"}, all.get(9));
+ }
+
}