如何使用Java 8流来查找更大值之前的所有值?

Pet*_*ete 31 java java-8 java-stream

用例

通过Katas在工作中发布的一些编码,我偶然发现了这个问题,我不知道如何解决.

使用Java 8 Streams,给定正整数列表,生成一个整数列表,其中整数前面有一个更大的值.

[10, 1, 15, 30, 2, 6]
Run Code Online (Sandbox Code Playgroud)

以上输入将产生:

[1, 15, 2]
Run Code Online (Sandbox Code Playgroud)

因为1在15之前,15在30之前,2在6之前.

非流解决方案

public List<Integer> findSmallPrecedingValues(final List<Integer> values) {

    List<Integer> result = new ArrayList<Integer>();
    for (int i = 0; i < values.size(); i++) {
        Integer next = (i + 1 < values.size() ? values.get(i + 1) : -1);
        Integer current = values.get(i);
        if (current < next) {
            result.push(current);
        }
    }
    return result;
}
Run Code Online (Sandbox Code Playgroud)

我试过的

我遇到的问题是我无法弄清楚如何在lambda中访问下一个.

return values.stream().filter(v -> v < next).collect(Collectors.toList());
Run Code Online (Sandbox Code Playgroud)

  • 是否可以检索流中的下一个值?
  • 我应该使用map并映射到a Pair以便访问下一个吗?

Rad*_*def 25

使用IntStream.range:

static List<Integer> findSmallPrecedingValues(List<Integer> values) {
    return IntStream.range(0, values.size() - 1)
        .filter(i -> values.get(i) < values.get(i + 1))
        .mapToObj(values::get)
        .collect(Collectors.toList());
}
Run Code Online (Sandbox Code Playgroud)

它肯定比具有大循环的命令式解决方案更好,但对于以惯用方式"使用流"的目标仍然有点meh.

是否可以检索流中的下一个值?

不,不是真的.我知道的最好的引用是在java.util.stream包描述中:

流的元素仅在流的生命期间访问过一次.像Iteratora 一样,必须生成一个新流来重新访问源的相同元素.

(检索正在操作的当前元素之外的元素意味着它们可以被访问多次.)

我们还可以通过其他方式在技术上做到这一点:

  • 有条不紊地(非常meh).
  • 使用流的iterator在技术上还是使用流.

  • 我认为这根本不是"meh".我认为这是最好的方法.许多人认为流是按从左到右的顺序处理值; 这导致了顺序的,状态突变的思维.它也容易出现边界错误.您可以在OP发布的传统循环方法中看到这一点.相比之下,如上所述,流式列表索引遵循基于矢量或数组的编程风格.对每个索引*i*进行计算,您可以很容易地证明结果对于所有*i*都是正确的.没有突变或顺序依赖,因此它很好地并行化.+1 (3认同)

Tag*_*eev 13

这不是一个纯Java8,但最近我发布了一个名为StreamEx的小型库,它有一个完全符合这个任务的方法:

// Find all numbers where the integer preceded a larger value.
Collection<Integer> numbers = Arrays.asList(10, 1, 15, 30, 2, 6);
List<Integer> res = StreamEx.of(numbers).pairMap((a, b) -> a < b ? a : null)
    .nonNull().toList();
assertEquals(Arrays.asList(1, 15, 2), res);
Run Code Online (Sandbox Code Playgroud)

pairMap操作内部使用的自定义实现spliterator.因此,您拥有非常干净的代码,而这些代码不依赖于源代码List还是其他内容.当然,它也适用于并行流.

为此任务提供了一个测试用例.

  • 为什么不。写了一个【新答案】(http://stackoverflow.com/questions/20470010/collect-successive-pairs-from-a-stream/30542049#30542049)。 (2认同)

Boh*_*ian 9

它不是单线(它是双线),但这有效:

List<Integer> result = new ArrayList<>();
values.stream().reduce((a,b) -> {if (a < b) result.add(a); return b;});
Run Code Online (Sandbox Code Playgroud)

而不是通过"查看下一个元素"来解决它,而是通过"查看前一个元素来解决它,它reduce()可以免费提供.我已经通过注入一个代码片段来填充它的预期用法,该片段根据比较填充列表. previous和current元素然后返回当前值,以便下一次迭代将其视为前一个元素.


一些测试代码:

List<Integer> result = new ArrayList<>();
IntStream.of(10, 1, 15, 30, 2, 6).reduce((a,b) -> {if (a < b) result.add(a); return b;});
System.out.println(result);
Run Code Online (Sandbox Code Playgroud)

输出:

[1, 15, 2]
Run Code Online (Sandbox Code Playgroud)

  • @JeffreyBosboom我很想整合一些在顺序情况下执行树减少的代码,只是为了打破每个人的非关联减速器函数.*非常诱惑. (5认同)

Ale*_* C. 5

如果流是顺序的还是并行的,则可接受的答案会很好,但List由于对的多次调用,如果基础不是随机访问,则可能会受到影响get

如果流是顺序的,则可以滚动此收集器:

public static Collector<Integer, ?, List<Integer>> collectPrecedingValues() {
    int[] holder = {Integer.MAX_VALUE};
    return Collector.of(ArrayList::new,
            (l, elem) -> {
                if (holder[0] < elem) l.add(holder[0]);
                holder[0] = elem;
            },
            (l1, l2) -> {
                throw new UnsupportedOperationException("Don't run in parallel");
            });
}
Run Code Online (Sandbox Code Playgroud)

和用法:

List<Integer> precedingValues = list.stream().collect(collectPrecedingValues());
Run Code Online (Sandbox Code Playgroud)

不过,您也可以实现一个收集器,以便对顺序流和并行流进行工作。唯一的事情是您需要应用最终的转换,但是在这里您可以控制List实现,因此不会受到get性能的影响。

这个想法是首先生成一个成对的列表(由int[]大小为2 的数组表示),其中包含由大小为2的窗口(间隔为1)切片的流中的值。当我们需要合并两个列表时,我们检查空度并将第一个列表的最后一个元素与第二个列表的第一个元素的间隙合并。然后,我们应用最终转换以仅过滤所需的值,并将它们映射为具有所需的输出。

它可能不像公认的答案那么简单,但是可以作为替代解决方案。

public static Collector<Integer, ?, List<Integer>> collectPrecedingValues() {
    return Collectors.collectingAndThen(
            Collector.of(() -> new ArrayList<int[]>(),
                    (l, elem) -> {
                        if (l.isEmpty()) l.add(new int[]{Integer.MAX_VALUE, elem});
                        else l.add(new int[]{l.get(l.size() - 1)[1], elem});
                    },
                    (l1, l2) -> {
                        if (l1.isEmpty()) return l2;
                        if (l2.isEmpty()) return l1;
                        l2.get(0)[0] = l1.get(l1.size() - 1)[1];
                        l1.addAll(l2);
                        return l1;
                    }), l -> l.stream().filter(arr -> arr[0] < arr[1]).map(arr -> arr[0]).collect(Collectors.toList()));
}
Run Code Online (Sandbox Code Playgroud)

然后,您可以将这两个收集器包装在实用程序收集器方法中,检查流是否与并行,isParallel然后确定要返回哪个收集器。

  • 只需使用4个CPU盒在随机创建的10k,100k和1M编号输入上概要分析所有提供的版本。并行版本是所有版本中最慢的(取决于输入大小,比并行版本慢2-3倍)。这是[gist](https://gist.github.com/amaembo/336df22ea21316feccf1),具有基准测试核心和结果(StreamEx测试需要JMH和StreamEx库) (2认同)
  • 当然,对于不支持随机访问的列表,可接受的答案肯定会表现不佳,并且对于任何其他流源(例如,“ BufferedReader”,流的串联等)根本不起作用。使用我的库,您有两个优点:它可与任何流源很好地协同工作,而并行化确实可以提高速度。缺点是:您必须使用更多代码。如果最快和灵活的解决方案也是简短的解决方案,那么我根本不会编写此库:-) (2认同)