从流中收集连续的对

Ale*_*sky 92 java java-8 java-stream

鉴于如下的流{ 0, 1, 2, 3, 4 },

我怎样才能最优雅地将其转换为给定的形式:

{ new Pair(0, 1), new Pair(1, 2), new Pair(2, 3), new Pair(3, 4) }

(假设,当然,我已经定义了类对)?

编辑:这不是严格关于整数或原始流.对于任何类型的流,答案应该是通用的.

Stu*_*rks 68

Java 8流库主要用于将流拆分为较小的块以进行并行处理,因此有状态的流水线阶段非常有限,并且不支持获取当前流元素的索引和访问相邻流元素等操作.

解决这些问题的典型方法当然有一些限制,即通过索引驱动流并依赖于在某些随机访问数据结构中处理值,例如可以从中检索元素的ArrayList.如果值为arrayList,则可以通过执行以下操作生成所请求的对:

    IntStream.range(1, arrayList.size())
             .mapToObj(i -> new Pair(arrayList.get(i-1), arrayList.get(i)))
             .forEach(System.out::println);
Run Code Online (Sandbox Code Playgroud)

当然,限制是输入不能是无限流.但是,此管道可以并行运行.

  • "输入不能是无限的流." 实际上,输入根本不是流.输入(`arrayList`)实际上是一个集合,这就是为什么我没有将它标记为答案.(但祝贺你的金徽章!) (5认同)

Tag*_*eev 31

我的StreamEx库扩展了标准流,pairMap为所有流类型提供了一种方法.对于原始流,它不会更改流类型,但可用于进行一些计算.最常见的用法是计算差异:

int[] pairwiseDiffs = IntStreamEx.of(input).pairMap((a, b) -> (b-a)).toArray();
Run Code Online (Sandbox Code Playgroud)

对于对象流,您可以创建任何其他对象类型.我的库没有提供任何新的用户可见数据结构Pair(这是库概念的一部分).但是,如果您有自己的Pair类并想要使用它,则可以执行以下操作:

Stream<Pair> pairs = IntStreamEx.of(input).boxed().pairMap(Pair::new);
Run Code Online (Sandbox Code Playgroud)

或者,如果你已经有一些Stream:

Stream<Pair> pairs = StreamEx.of(stream).pairMap(Pair::new);
Run Code Online (Sandbox Code Playgroud)

此功能使用自定义spliterator实现.它的开销很低,可以很好地并行化.当然它适用于任何流源,而不仅仅是随机访问列表/数组,就像许多其他解决方案一样.在许多测试中,它表现得非常好.这是一个JMH基准测试,我们使用不同的方法找到更大值之前的所有输入值(参见问题).

  • PS,此解决方案适用于并行流. (2认同)
  • @AleksandrDubinsky:只使用[`StreamEx.of(stream)`](http://amaembo.github.io/streamex/javadoc/javax/util/streamex/StreamEx.html#of-java.util.stream.Stream- ).还有其他方便的静态方法来从`Collection`,array,`Reader`等创建流.编辑答案. (2认同)

mis*_*off 16

这不是优雅,它是一个hackish解决方案,但适用于无限流

Stream<Pair> pairStream = Stream.iterate(0, (i) -> i + 1).map( // natural numbers
    new Function<Integer, Pair>() {
        Integer previous;

        @Override
        public Pair apply(Integer integer) {
            Pair pair = null;
            if (previous != null) pair = new Pair(previous, integer);
            previous = integer;
            return pair;
        }
    }).skip(1); // drop first null
Run Code Online (Sandbox Code Playgroud)

现在,您可以将流限制为所需的长度

pairStream.limit(1_000_000).forEach(i -> System.out.println(i));
Run Code Online (Sandbox Code Playgroud)

PS我希望有更好的解决方案,比如clojure(partition 2 1 stream)

  • 这与stream框架的设计完全相反,并且直接违反了map API的契约,因为匿名函数是*not*stateless.尝试使用并行流和更多数据运行它,以便流框架创建更多工作线程,您将看到结果:不经常随机"错误"几乎不可能重现并且难以检测,直到您有足够的数据(在生产中?).这可能是灾难性的. (12认同)
  • 值得称赞的是,匿名类有时是lambdas的有用替代品. (6认同)
  • @AleksandrDubinsky关于限制/跳过可并行化是不正确的; JDK中提供的实现确实并行工作.由于操作与遇到订单相关联,因此并行化可能并不总能提供性能优势,但在高Q情况下,它可以. (4认同)
  • @AleksandrDubinsky不正确.如果流是*无序它可跳过一个随机元素*(没有定义的遭遇顺序,因此在逻辑上有*是*无"第一"或"第n"元件,只是元素.),但流是否是有序的或无序的,则跳过一直能够并行工作.如果流是有序的,那么提取的并行性就会降低,但它仍然是并行的. (4认同)
  • @aepurniet我认为它无法正常工作.根据`parallelStream`文档:"为了保持正确的行为,这些行为参数必须是非干扰的,并且在大多数情况下必须是无状态的" (2认同)
  • @MarioRossi Streams框架不存在**只**写并行代码.不幸的是,它的屁股位于栅栏的两侧,许多程序员使用它来编写顺序代码.甚至还有内置的方法无法并行化(例如`skip`). (2认同)

Tom*_*wek 14

我已经实现了一个spliterator包装器,它从原始的spliterator中获取每个n元素T并生成List<T>:

public class ConsecutiveSpliterator<T> implements Spliterator<List<T>> {

    private final Spliterator<T> wrappedSpliterator;

    private final int n;

    private final Deque<T> deque;

    private final Consumer<T> dequeConsumer;

    public ConsecutiveSpliterator(Spliterator<T> wrappedSpliterator, int n) {
        this.wrappedSpliterator = wrappedSpliterator;
        this.n = n;
        this.deque = new ArrayDeque<>();
        this.dequeConsumer = deque::addLast;
    }

    @Override
    public boolean tryAdvance(Consumer<? super List<T>> action) {
        deque.pollFirst();
        fillDeque();
        if (deque.size() == n) {
            List<T> list = new ArrayList<>(deque);
            action.accept(list);
            return true;
        } else {
            return false;
        }
    }

    private void fillDeque() {
        while (deque.size() < n && wrappedSpliterator.tryAdvance(dequeConsumer))
            ;
    }

    @Override
    public Spliterator<List<T>> trySplit() {
        return null;
    }

    @Override
    public long estimateSize() {
        return wrappedSpliterator.estimateSize();
    }

    @Override
    public int characteristics() {
        return wrappedSpliterator.characteristics();
    }
}
Run Code Online (Sandbox Code Playgroud)

以下方法可用于创建连续流:

public <E> Stream<List<E>> consecutiveStream(Stream<E> stream, int n) {
    Spliterator<E> spliterator = stream.spliterator();
    Spliterator<List<E>> wrapper = new ConsecutiveSpliterator<>(spliterator, n);
    return StreamSupport.stream(wrapper, false);
}
Run Code Online (Sandbox Code Playgroud)

样品用法:

consecutiveStream(Stream.of(0, 1, 2, 3, 4, 5), 2)
    .map(list -> new Pair(list.get(0), list.get(1)))
    .forEach(System.out::println);
Run Code Online (Sandbox Code Playgroud)

  • +1我认为这是一项很好的工作,除了分区大小之外,还应该推广到任何步长.有很多需要`(分区大小步骤)`函数,这是获得它的最佳方法. (4认同)
  • 考虑使用`ArrayDeque`来提高性能,而不是使用`LinkedList`. (3认同)

Sam*_*s33 13

您可以使用Stream.reduce()方法执行此操作(我没有看到使用此技术的任何其他答案).

public static <T> List<Pair<T, T>> consecutive(List<T> list) {
    List<Pair<T, T>> pairs = new LinkedList<>();
    list.stream().reduce((a, b) -> {
        pairs.add(new Pair<>(a, b));
        return b;
    });
    return pairs;
}
Run Code Online (Sandbox Code Playgroud)

  • 请检查问题,这是预期的行为@Aleksandr Dubinsky (3认同)
  • 啊,对不起 想一想,我写了. (2认同)
  • 这是一个非常聪明的想法!我在这种方法中看到的唯一问题是归约函数不是*纯粹的*(它取决于外部“pairs”对象)。因此,如果并发运行,则无法保证其语义正确性。一种可能的解决方案是使用线程安全的数据结构,例如“Vector”。 (2认同)

Joh*_*ean 6

您可以使用滑动操作符在cyclops-react(我参与此库)中执行此操作.

  LazyFutureStream.of( 0, 1, 2, 3, 4 )
                  .sliding(2)
                  .map(Pair::new);
Run Code Online (Sandbox Code Playgroud)

要么

   ReactiveSeq.of( 0, 1, 2, 3, 4 )
                  .sliding(2)
                  .map(Pair::new);
Run Code Online (Sandbox Code Playgroud)

假设Pair构造函数可以接受包含2个元素的Collection.

如果要按4分组,则增加2,也支持.

     ReactiveSeq.rangeLong( 0L,Long.MAX_VALUE)
                .sliding(4,2)
                .forEach(System.out::println);
Run Code Online (Sandbox Code Playgroud)

在cyclops-streams StreamUtils类中还提供了用于在java.util.stream.Stream上创建滑动视图的Equivalant静态方法.

       StreamUtils.sliding(Stream.of(1,2,3,4),2)
                  .map(Pair::new);
Run Code Online (Sandbox Code Playgroud)

注意: - 对于单线程操作,ReactiveSeq更合适.LazyFutureStream扩展了ReactiveSeq,但主要用于并发/并行使用(它是一个期货流).

LazyFutureStream扩展了ReactiveSeq,它从令人敬畏的jOOλ(扩展了java.util.stream.Stream)扩展了Seq,因此Lukas提供的解决方案也适用于Stream类型.对于任何感兴趣的人来说,窗口/滑动运算符之间的主要区别在于明显的相对功率/复杂度权衡以及与无限流一起使用的适合性(滑动不消耗流,而是流动时的缓冲区).


Ale*_* C. 5

proton-pack 库提供窗口功能。给定一个 Pair 类和一个 Stream,你可以这样做:

Stream<Integer> st = Stream.iterate(0 , x -> x + 1);
Stream<Pair<Integer, Integer>> pairs = StreamUtils.windowed(st, 2, 1)
                                                  .map(l -> new Pair<>(l.get(0), l.get(1)))
                                                  .moreStreamOps(...);
Run Code Online (Sandbox Code Playgroud)

现在该pairs流包含:

(0, 1)
(1, 2)
(2, 3)
(3, 4)
(4, ...) and so on
Run Code Online (Sandbox Code Playgroud)


Luk*_*der 5

寻找连续的对

\n\n

如果您愿意使用第三方库并且不需要并行性,那么jOO\xce\xbb提供 SQL 风格的窗口函数,如下所示

\n\n
System.out.println(\nSeq.of(0, 1, 2, 3, 4)\n   .window()\n   .filter(w -> w.lead().isPresent())\n   .map(w -> tuple(w.value(), w.lead().get())) // alternatively, use your new Pair() class\n   .toList()\n);\n
Run Code Online (Sandbox Code Playgroud)\n\n

屈服

\n\n\n\n
[(0, 1), (1, 2), (2, 3), (3, 4)]\n
Run Code Online (Sandbox Code Playgroud)\n\n

lead()函数按遍历顺序从窗口访问下一个值。

\n\n

查找连续的三元组/四元组/n元组

\n\n

评论中的一个问题是要求一个更通用的解决方案,其中不应收集对,而是应收集 n 元组(或可能是列表)。因此,这是一种替代方法:

\n\n
[(0, 1), (1, 2), (2, 3), (3, 4)]\n
Run Code Online (Sandbox Code Playgroud)\n\n

生成列表列表

\n\n\n\n
[[0, 1, 2], [1, 2, 3], [2, 3, 4]]\n
Run Code Online (Sandbox Code Playgroud)\n\n

如果没有filter(w -> w.count() == n),结果将是

\n\n\n\n
[[0, 1, 2], [1, 2, 3], [2, 3, 4], [3, 4], [4]]\n
Run Code Online (Sandbox Code Playgroud)\n\n

免责声明:我在 jOO\xce\xbb 背后的公司工作

\n


Evg*_*eev 5

Streams.zip(..)在 Guava 中可用,对于那些依赖它的人。

例子:

Streams.zip(list.stream(),
            list.stream().skip(1),
            (a, b) -> System.out.printf("%s %s\n", a, b));
Run Code Online (Sandbox Code Playgroud)


M. *_*tin 2

JEP 461:Stream Gatherers Java 22 预览语言功能对此有内置支持:

\n
Stream.of(0, 1, 2, 3, 4)\n        .gather(Gatherers.windowSliding(2))\n        .map(list -> new Pair(list.get(0), list.get(1)))\n        .toList();\n
Run Code Online (Sandbox Code Playgroud)\n

这使用新Stream.gather方法和新的内置Gatherers.windowSliding收集器将初始Stream<Integer>( [0, 1, 2, 3, 4]) 转换为成对Stream<List<Integer>>( [[0, 1], [1, 2], [2, 3], [3, 4]])。Pair然后使用现有方法将这些列表中的每一个转换为列表Stream.map

\n

Java文档

\n

Gatherer:

\n
\n

将输入元素流转换为输出元素流的中间操作,可以选择在到达上游末尾时应用最终操作。[\xe2\x80\xa6]

\n

[\xe2\x80\xa6]

\n

收集操作的示例有很多,包括但不限于:将元素分组为批次(窗口函数);对连续相似的元素进行去重;增量累加功能(前缀扫描);增量重新排序功能等。该类Gatherers提供常见收集操作的实现。

\n
\n

Stream.gather:

\n
\n

返回一个流,其中包含将给定收集器应用于该流的元素的结果。

\n
\n

Gatherers.windowSliding

\n
\n

返回一个 Gatherer,它将元素收集到给定大小的窗口(按遇到顺序排列的元素组)中,其中每个后续窗口都包含前一个窗口的所有元素(除了最近的元素之外),并在流中添加下一个元素。[\xe2\x80\xa6]

\n

例子:

\n
// will contain: [[1, 2], [2, 3], [3, 4], [4, 5], [5, 6], [6, 7], [7, 8]]\nList<List<Integer>> windows2 =\n    Stream.of(1,2,3,4,5,6,7,8).gather(Gatherers.windowSliding(2)).toList();\n\n// will contain: [[1, 2, 3, 4, 5, 6], [2, 3, 4, 5, 6, 7], [3, 4, 5, 6, 7, 8]]\nList<List<Integer>> windows6 =\n    Stream.of(1,2,3,4,5,6,7,8).gather(Gatherers.windowSliding(6)).toList();\n
Run Code Online (Sandbox Code Playgroud)\n
\n


归档时间:

查看次数:

47193 次

最近记录:

6 年 前