Java 群值序列
我想知道是否有任何巧妙的方法可以使用新的流API来“分组”值序列 e、 g.将一系列整数拆分为整数组,每组为升序:Java 群值序列,java,sorting,lambda,java-8,java-stream,Java,Sorting,Lambda,Java 8,Java Stream,我想知道是否有任何巧妙的方法可以使用新的流API来“分组”值序列 e、 g.将一系列整数拆分为整数组,每组为升序: IntStream seq = IntStream.of(1, 2, 3, -1, -1, 1, 2, 1, 2); IntFunction next = i -> i + 1; // DESIRED OUTPUT: [[1,2,3], [-1], [-1], [1,2], [1,2]] 不幸的是,流API不太适合处理涉及流元素上依赖操作的问题,如下面的问题 但是,您可以
IntStream seq = IntStream.of(1, 2, 3, -1, -1, 1, 2, 1, 2);
IntFunction next = i -> i + 1;
// DESIRED OUTPUT: [[1,2,3], [-1], [-1], [1,2], [1,2]]
不幸的是,流API不太适合处理涉及流元素上依赖操作的问题,如下面的问题 但是,您可以为此使用库:
public static void main(String[] args) {
IntStream seq = IntStream.of(1, 2, 3, -1, -1, 1, 2, 1, 2);
IntUnaryOperator next = i -> i + 1;
List<List<Integer>> result =
IntStreamEx.of(seq).boxed().groupRuns((i1, i2) -> next.applyAsInt(i1) == i2).toList();
System.out.println(result); // prints "[[1, 2, 3], [-1], [-1], [1, 2], [1, 2]]"
}
publicstaticvoidmain(字符串[]args){
IntStream seq=IntStream.of(1,2,3,-1,-1,1,2,1,2);
下一个插管器=i->i+1;
列表结果=
IntStreamEx.of(seq).boxed().groupRuns((i1,i2)->next.applyAsInt(i1)==i2.toList();
System.out.println(结果);//打印“[[1,2,3]、-1]、-1]、[1,2]、[1,2]”
}
这将所有连续整数分组到一个
列表中,其中第二个整数等于应用于第一个整数的next
函数。最后,此流被收集到列表中
没有@Tunaki solution那么优雅,而是使用“纯”Java-8流:
IntStream seq = IntStream.of(1, 2, 3, -1, -1, 1, 2, 1, 2);
Deque<Deque<Integer>> r = new ArrayDeque<>(singleton(new ArrayDeque<>()));
seq.filter(i -> !r.getLast().isEmpty() && r.getLast().getLast() + 1 != i || !r.getLast().add(i))
.forEach(i -> r.add(new ArrayDeque<>(singleton(i))));
System.out.println(r); // prints: [[1, 2, 3], [-1], [-1], [1, 2], [1, 2]]
IntStream seq=IntStream.of(1,2,3,-1,-1,1,1,2);
Deque r=new ArrayDeque(singleton(new ArrayDeque()));
seq.filter(i->!r.getLast().isEmpty()&&r.getLast().getLast()+1!=i||!r.getLast().add(i))
.forEach(i->r.add(新ArrayDeque(singleton(i)));
System.out.println(r);//打印:[[1,2,3],-1],-1],[1,2],[1,2]]
这里只是为了代码的优雅,我使用了Deque
类来使用getLast()
方法(对于List
来说,它不会那么紧凑)。如果您愿意操作内存中的数据结构,比如数组或列表,那么在标准Java8中只需几个步骤就可以做到这一点。这可以使用数组编程技术来完成,如我的。使用一些类似于中使用的巧妙条件,可以以一种简洁的方式处理边缘情况
关键的洞察是要认识到一个新的段(或组)从所需谓词未满足的每一点开始。也就是说,新段开始于seq[i-1]+1!=seq[i]
。让我们在输入上运行IntStream
,筛选此属性的索引,并将结果存储在某个数组x
:
int[] seq = { 1, 2, 3, -1, -1, 1, 2, 1, 2 };
int[] x = IntStream.range(1, seq.length)
.filter(i -> seq[i-1] + 1 != seq[i])
.toArray();
导致
[3, 4, 5, 7]
这仅为我们提供分段的内部边界。要获得段的起点和终点,我们需要固定第一段的起点和最后一段的终点。我们调整索引范围并向过滤器添加一些条件:
int[] x = IntStream.rangeClosed(0, seq.length)
.filter(i -> i == 0 || i == seq.length ||
seq[i-1] + 1 != seq[i])
.toArray();
[0, 3, 4, 5, 7, 9]
现在,每个相邻的索引对都是原始数组的子范围。我们可以使用另一个流来提取这些子范围,从而得到所需的结果:
int[][] result =
IntStream.range(0, x.length - 1)
.mapToObj(i -> Arrays.copyOfRange(seq, x[i], x[i+1]))
.toArray(int[][]::new);
[[1, 2, 3], [-1], [-1], [1, 2], [1, 2]]
这可以被提取到一个函数中,该函数本身接受一个“next”函数,该函数计算段中的下一个值。也就是说,对于任何元素,如果其右侧的元素与下一个函数的结果匹配,则元素位于同一段中;否则它是一个段边界。代码如下:
int[][] segments(int[] seq, IntUnaryOperator next) {
int[] x = IntStream.rangeClosed(0, seq.length)
.filter(i -> i == 0 || i == seq.length ||
next.applyAsInt(seq[i-1]) != seq[i])
.toArray();
return IntStream.range(0, x.length - 1)
.mapToObj(i -> Arrays.copyOfRange(seq, x[i], x[i+1]))
.toArray(int[][]::new);
}
你可以这样称呼它:
int[] seq = { 1, 2, 3, -1, -1, 1, 2, 1, 2 };
System.out.println(Arrays.deepToString(segments(seq, i -> i + 1)));
[[1, 2, 3], [-1], [-1], [1, 2], [1, 2]]
更改下一个函数允许以不同的方式拆分段。例如,要将数组拆分为相等值的段,可以执行以下操作:
int[] seq = { 2, 2, 1, 3, 3, 1, 1, 1, 4, 4, 4 };
System.out.println(Arrays.deepToString(segments(seq, i -> i)));
[[2, 2], [1], [3, 3], [1, 1, 1], [4, 4, 4]]
使用像这样的下一个函数的困难在于,属于某个段的值的条件是有限的。如果提供一个与相邻值进行比较的谓词来测试它们是否在同一段中,那就更好了。如果我们愿意支付拳击费用,我们可以使用BiPredicate
:
int[][] segments(int[] input, BiPredicate<Integer, Integer> pred) {
int[] x = IntStream.rangeClosed(0, input.length)
.filter(i -> i == 0 || i == input.length ||
!pred.test(input[i-1], input[i]))
.toArray();
return IntStream.range(0, x.length - 1)
.mapToObj(i -> Arrays.copyOfRange(input, x[i], x[i+1]))
.toArray(int[][]::new);
}
这可以专门用于在两个int
值上使用一个基本的双谓词,也可以推广为允许在任何类型的输入上使用任何类型的BiPredicate
。输出不应该是这样的:[[1,2,3],-1],-1,1,2],[1,2]
?@Flown No,因为1!=next.apply(-1)
Ah oknext
是一个谓词。虽然没有您的想法那么优雅,但它确实可以用纯Java-8流来完成。谢谢!看起来StreamEx可以帮我省去很多麻烦!应该注意的是,这种解决方案滥用了API(特别是,传递给的谓词
。过滤器
必须是无状态的。因此,这个解决方案无法并行化。@TagirValeev可以是Tanuki的解决方案吗?是的,实际上,在大输入时,您可能会有加速。每个StreamEx功能都能正确地处理并行化,而且大多数功能实际上都能从并行化中获益。这是4核机器上基准测试和结果的要点。嗯,速度提升不是很显著,但仍然很显著。串行版本的工作速度与您的大致相同。
int[] seq = { 3, 1, 4, 1, 5, 9, 2, 6, 5, 3 };
System.out.println(Arrays.deepToString(segments(seq, (a, b) -> b > a)));
[[3], [1, 4], [1, 5, 9], [2, 6], [5], [3]]