如何将字符串流转换为字符串流对?

How can I convert a Stream of Strings to Stream of String pairs?

我想获取字符串流并将其转换为单词对流。例如:

我有:{ "A", "Apple", "B", "Banana", "C", "Carrot" }

我要:{ ("A", "Apple"), ("Apple", "B"), ("B", "Banana"), ("Banana", "C") }.

这与 Zipping streams using JDK8 with lambda (java.util.stream.Streams.zip)

中概述的压缩几乎相同

但是,这会产生: { (A, Apple), (B, Banana), (C, Carrot) }

以下代码有效,但显然是错误的方法(不是线程安全的等等):

static String buffered = null;

static void output(String s) {
    String result = null;
    if (buffered != null) {
        result = buffered + "," + s;
    } else {
        result = null;
    }

    buffered = s;
    System.out.println(result);
}

// ***** 

Stream<String> testing = Stream.of("A", "Apple", "B", "Banana", "C", "Carrot");
testing.forEach(s -> {output(s);});

这应该做你想做的,基于@njzk2 两次使用流的评论,在第二种情况下跳过第一个元素。它使用您在原始问题中 link 的 zip 方法。

public static void main(String[] args) {
  List<String> input = Arrays.asList("A", "Apple", "B", "Banana", "C", "Carrot");
  List<List<String>> paired = zip(input.stream(),
                                  input.stream().skip(1),
                                  (a, b) -> Arrays.asList(a, b))
                              .collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
  System.out.println(paired);
}

这会输出一个 List<List<String>> 内容:

[[A, Apple], [Apple, B], [B, Banana], [Banana, C], [C, Carrot]]

在评论中,您询问了如果您已经有 Stream 如何执行此操作。不幸的是,这很难,因为 Streams 不是有状态的,并且 Stream 中实际上没有 "adjacent" 元素的概念。有个good discussion on this here.

我可以想到两种方法,但我认为您不会喜欢其中任何一种:

  1. Stream 转换为 List,然后执行我上面的解决方案。丑陋,但只要 Stream 不是无限的并且性能不是很重要。
  2. 使用 ,只要您使用的是 StreamEx 而不是 Stream,并且愿意添加对第三方库的依赖。

与此讨论相关的还有这个问题:Can I duplicate a Stream in Java 8?;这对你的问题来说不是好消息,但值得一读,并且可能有一个对你更有吸引力的解决方案。

您可以使用我的 StreamEx library which enhances standard Stream API. There is a method pairMap,它完全满足您的需要:

StreamEx.of("A", "Apple", "B", "Banana", "C", "Carrot")
        .pairMap((a, b) -> a+","+b)
        .forEach(System.out::println);

输出:

A,Apple
Apple,B
B,Banana
Banana,C
C,Carrot

pairMap参数是将一对相邻元素转换为适合您需要的东西的函数。如果你的项目中有Pair class,你可以使用.pairMap(Pair::new)来获取对流。如果你想创建一个二元列表流,你可以使用:

List<List<String>> list = StreamEx.of("A", "Apple", "B", "Banana", "C", "Carrot")
                                    .pairMap((a, b) -> StreamEx.of(a, b).toList())
                                    .toList();
System.out.println(list); // [[A, Apple], [Apple, B], [B, Banana], [Banana, C], [C, Carrot]]

这适用于任何元素源(您可以使用 StreamEx.of(collection)StreamEx.of(stream) 等),如果您在 pairMap 之前有更多流操作并且对并行非常友好,则可以正常工作处理(与涉及流压缩的解决方案不同)。

如果您的输入是具有快速随机访问的 List 并且您实际上想要 List<List<String>> 作为结果,那么在我的库中使用 ofSubLists:

List<String> input = Arrays.asList("A", "Apple", "B", "Banana", "C", "Carrot");
List<List<String>> list = StreamEx.ofSubLists(input, 2, 1).toList();
System.out.println(list); // [[A, Apple], [Apple, B], [B, Banana], [Banana, C], [C, Carrot]]

这里在幕后为每个输入列表位置调用 input.subList(i, i+2),因此您的数据不会复制到新列表,但会创建引用原始列表的子列表。

如果你:

  1. 不喜欢创建包含流中所有字符串的列表的想法
  2. 不想使用外部库
  3. 喜欢动手

然后您可以创建一个方法来使用 Java 8 个低级流构建器对流中的元素进行分组 StreamSupport and Spliterator:

class StreamUtils {
    public static<T> Stream<List<T>> sliding(int size, Stream<T> stream) {
        return sliding(size, 1, stream);
    }

    public static<T> Stream<List<T>> sliding(int size, int step, Stream<T> stream) {
        Spliterator<T> spliterator = stream.spliterator();
        long estimateSize;

        if (!spliterator.hasCharacteristics(Spliterator.SIZED)) {
            estimateSize = Long.MAX_VALUE;
        } else if (size > spliterator.estimateSize()) {
            estimateSize = 0;
        } else {
            estimateSize = (spliterator.estimateSize() - size) / step + 1;
        }

        return StreamSupport.stream(
                new Spliterators.AbstractSpliterator<List<T>>(estimateSize, spliterator.characteristics()) {
                    List<T> buffer = new ArrayList<>(size);

                    @Override
                    public boolean tryAdvance(Consumer<? super List<T>> consumer) {
                        while (buffer.size() < size && spliterator.tryAdvance(buffer::add)) {
                            // Nothing to do
                        }

                        if (buffer.size() == size) {
                            List<T> keep = new ArrayList<>(buffer.subList(step, size));
                            consumer.accept(buffer);
                            buffer = keep;
                            return true;
                        }
                        return false;
                    }
                }, stream.isParallel());
    }
}

方法和参数命名的灵感来自 Scala 对应项。

我们来测试一下:

Stream<String> testing = Stream.of("A", "Apple", "B", "Banana", "C", "Carrot");
System.out.println(StreamUtils.sliding(2, testing).collect(Collectors.toList()));

[[A, Apple], [Apple, B], [B, Banana], [Banana, C], [C, Carrot]]

不重复元素呢:

Stream<String> testing = Stream.of("A", "Apple", "B", "Banana", "C", "Carrot");
System.out.println(StreamUtils.sliding(2, 2, testing).collect(Collectors.toList()));

[[A, Apple], [B, Banana], [C, Carrot]]

现在无限 Stream:

StreamUtils.sliding(5, Stream.iterate(0, n -> n + 1))
        .limit(5)
        .forEach(System.out::println);

[0, 1, 2, 3, 4]
[1, 2, 3, 4, 5]
[2, 3, 4, 5, 6]
[3, 4, 5, 6, 7]
[4, 5, 6, 7, 8]

这是创建 List<List<String>> 对的最少量代码:

List<List<String>> pairs = new LinkedList<>();
testing.reduce((a, b)-> {pairs.add(Arrays.asList(a,b)); return b;});