Merge pull request #4166 from qieugxi/hutool-1127-4

fix issue 4165
This commit is contained in:
Golden Looly 2025-11-28 09:55:18 +08:00 committed by GitHub
commit 1d9b0ebbd2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 164 additions and 37 deletions

View File

@ -213,68 +213,147 @@ public class Arrangement implements Serializable {
private static class ArrangementIterator implements Iterator<String[]> {
private final String[] datas;
private final int n;
private final int m;
private final boolean[] visited;
private final String[] buffer;
private final Deque<Integer> 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;
}
}

View File

@ -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<String[]> 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<String[]> 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<String[]> iterResult = new ArrayList<>();
for (String[] perm : arrangement.iterate(5)) {
iterResult.add(perm);
}
assertTrue(iterResult.isEmpty());
}
}