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 1ade5a325..19f4493b8 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 @@ -213,68 +213,147 @@ public class Arrangement implements Serializable { private static class ArrangementIterator implements Iterator { private final String[] datas; + private final int n; private final int m; private final boolean[] visited; private final String[] buffer; - private final Deque stack = new ArrayDeque<>(); - boolean end = false; + + // 每一层记录当前尝试的下标,-1表示还未尝试 + private final int[] indices; + private int depth; + private boolean end; + + // 预取下一个元素 + private String[] nextItem; + private boolean nextPrepared; ArrangementIterator(String[] datas, int m) { this.datas = datas; + this.n = datas.length; this.m = m; - this.visited = new boolean[datas.length]; - this.buffer = new String[m]; - // 初始化 dfs 栈 - stack.push(0); + this.visited = new boolean[n]; + this.nextItem = null; + this.nextPrepared = false; + + if (m < 0 || m > n) { + // 无效或无解,直接结束 + this.indices = new int[Math.max(1, m)]; + this.buffer = new String[Math.max(1, m)]; + this.depth = -1; + this.end = true; + } else if (m == 0) { + // m == 0: 只返回一个空数组 + this.indices = new int[0]; + this.buffer = new String[0]; + this.depth = 0; + this.end = false; + } else { + this.indices = new int[m]; + Arrays.fill(this.indices, -1); + this.buffer = new String[m]; + this.depth = 0; + this.end = false; + } } @Override public boolean hasNext() { - return !end; + if (end) return false; + if (nextPrepared) return nextItem != null; + prepareNext(); + return nextItem != null; } @Override public String[] next() { - while (!stack.isEmpty()) { - int depth = stack.size() - 1; + if (end && !nextPrepared) { + throw new NoSuchElementException(); + } + if (!nextPrepared) { + prepareNext(); + } + if (nextItem == null) { + throw new NoSuchElementException(); + } + String[] ret = nextItem; + // 清除预取缓存,下一次需要重新准备 + nextItem = null; + nextPrepared = false; + // 如果m == 0,该项是唯一项,迭代结束 + if (m == 0) { + end = true; + } + return ret; + } - int idx = stack.pop(); - if (idx >= datas.length) { - // 这一层遍历结束 - if (!stack.isEmpty()) { - int prev = stack.pop(); - stack.push(prev + 1); + /** + * 将状态推进到下一个可返回的排列并把它放入 nextItem。 + * 如果无更多排列,则将 end=true 并把 nextItem 置为 null。 + */ + private void prepareNext() { + // 已经准备过或已结束 + if (nextPrepared || end) { + nextPrepared = true; + return; + } + + // special-case m == 0 + if (m == 0) { + nextItem = new String[0]; + nextPrepared = true; + // do not set end here; end will be set after returning this element in next() + return; + } + + // 非递归模拟DFS,直到找到一个可返回的排列或穷尽 + while (depth >= 0) { + int start = indices[depth] + 1; + boolean found = false; + for (int i = start; i < n; i++) { + if (!visited[i]) { + // 如果当前层之前有选过一个元素,要先取消之前选中的 visited + if (indices[depth] != -1) { + visited[indices[depth]] = false; + } + indices[depth] = i; + visited[i] = true; + buffer[depth] = datas[i]; + found = true; + break; } + } + + if (!found) { + // 本层没有可用元素,回溯 + if (indices[depth] != -1) { + visited[indices[depth]] = false; + indices[depth] = -1; + } + depth--; 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; + // 若已达到输出深度,准备输出(但不抛出) + if (depth == m - 1) { + nextItem = Arrays.copyOf(buffer, m); + // 取消当前visited,为下一次在同一层寻找下一个候选做准备 + visited[indices[depth]] = false; + // 保持 depth 不变(下一次 prepare 会从 indices[depth]+1 开始寻找) + nextPrepared = true; + return; + } else { + // 向下一层深入:初始化下一层为-1并继续循环 + depth++; + if (depth < m) { + indices[depth] = -1; } } - - // 已访问则跳过 - stack.push(idx + 1); } + // 若循环结束,说明已经穷尽所有可能 end = true; - return null; + nextItem = null; + nextPrepared = true; } } 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 b160d7fa2..1f9bce12d 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 @@ -4,6 +4,7 @@ import cn.hutool.core.lang.Console; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import java.util.ArrayList; import java.util.List; import static org.junit.jupiter.api.Assertions.*; @@ -145,4 +146,51 @@ public class ArrangementTest { assertArrayEquals(new String[]{"1", "2", "3"}, all.get(9)); } + // ---------------------------------------------------- + // 迭代器测试 + // ---------------------------------------------------- + @Test + public void iteratorTest() { + Arrangement arrangement = new Arrangement(new String[]{"1", "2", "3"}); + + // 测试 m=2 的情况 + List iterResult = new ArrayList<>(); + for (String[] perm : arrangement.iterate(2)) { + iterResult.add(perm); + } + + assertEquals(6, iterResult.size()); + assertArrayEquals(new String[]{"1", "2"}, iterResult.get(0)); + assertArrayEquals(new String[]{"1", "3"}, iterResult.get(1)); + assertArrayEquals(new String[]{"2", "1"}, iterResult.get(2)); + assertArrayEquals(new String[]{"2", "3"}, iterResult.get(3)); + assertArrayEquals(new String[]{"3", "1"}, iterResult.get(4)); + assertArrayEquals(new String[]{"3", "2"}, iterResult.get(5)); + } + + @Test + public void iteratorFullTest() { + Arrangement arrangement = new Arrangement(new String[]{"1", "2", "3"}); + + // 测试全排列的情况 + List iterResult = new ArrayList<>(); + for (String[] perm : arrangement.iterate(3)) { + iterResult.add(perm); + } + + assertEquals(6, iterResult.size()); + } + + @Test + public void iteratorBoundaryTest() { + Arrangement arrangement = new Arrangement(new String[]{"1", "2", "3"}); + + // 测试 m > n 的情况 + List iterResult = new ArrayList<>(); + for (String[] perm : arrangement.iterate(5)) { + iterResult.add(perm); + } + assertTrue(iterResult.isEmpty()); + } + }