简体   繁体   English

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

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

I want to take a stream of strings and turn it into a stream of word pairs. 我想取一串字符串并将其转换为单词对流。 eg: 例如:

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

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

This is nearly the same as Zipping, as outlined at Zipping streams using JDK8 with lambda (java.util.stream.Streams.zip) 这与使用带有lambda的JDK8的Zipping流中概述的Zipping几乎相同(java.util.stream.Streams.zip)

However, that produces: { (A, Apple), (B, Banana), (C, Carrot) } 然而,这产生: { (A, Apple), (B, Banana), (C, Carrot) }

The following code works, but is clearly the wrong way to do it (not thread safe etc etc): 以下代码有效,但显然是错误的方法(不是线程安全等):

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);});

This should do what you want, based on @njzk2's comment of using the stream twice, skipping the first element in the second case. 这应该做你想要的,基于@ njzk2的两次使用流的注释,跳过第二种情况下的第一个元素。 It uses the zip method that you link in your original question. 它使用您在原始问题中链接的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);
}

This outputs a List<List<String>> with contents: 这将输出List<List<String>> ,其内容为:

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

In the comments, you asked how to do this if you already have a Stream . 在评论中,如果您已有Stream ,则询问如何执行此操作。 Unfortunately, it's difficult, because Streams are not stateful, and there isn't really a concept of the "adjacent" element in the Stream . 不幸的是,这是困难的,因为Streams是无状态的,并没有真正在的“相邻”元素的概念Stream There is a good discussion on this here . 这里有一个很好的讨论

I can think of two ways to do it, but I don't think you're going to like either of them: 我可以想到两种方法,但我不认为你会喜欢它们中的任何一种:

  1. Convert the Stream to a List , and then do my solution above. Stream转换为List ,然后执行上面的解决方案。 Ugly, but works as long as the Stream isn't infinite and performance doesn't matter very much. 丑陋,但只要Stream不是无限的,并且性能无关紧要。
  2. Use @TagirValeev's answer below , as long as you are using a StreamEx and not a Stream , and willing to add a dependency on a third party library. 使用@ TagirValeev的答案 ,只要您使用的是StreamEx而不是Stream ,并且愿意在第三方库中添加依赖项。

Also relevant to this discussion is this question here: Can I duplicate a Stream in Java 8? 与此讨论相关的还有以下问题: 我可以在Java 8中复制Stream吗? ; ; it's not good news for your problem, but is worth reading and may have a solution that's more appealing to you. 这对你的问题不是好消息,但值得一读,可能会有一个对你更有吸引力的解决方案。

If you: 如果你:

  1. Don't like the idea of creating a list with all strings from your stream 不喜欢使用流中的所有字符串创建列表的想法
  2. Don't want to use external libraries 不想使用外部库
  3. Like to get your hands dirty 喜欢弄脏你的手

Then you can create a method to group elements from a stream using Java 8 low-level stream builders StreamSupport and Spliterator : 然后,您可以创建一个方法,使用Java 8低级流构建器StreamSupportSpliterator

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());
    }
}

Methods and parameters naming was inspired in their Scala counterparts. 方法和参数命名的灵感来自他们的Scala对应物。

Let's test it: 我们来测试一下:

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]] [[A,Apple],[Apple,B],[B,Banana],[香蕉,C],[C,胡萝卜]]

What about not repeating elements: 不重复元素怎么样:

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]] [[A,Apple],[B,香蕉],[C,胡萝卜]]

And now with an infinite Stream : 现在有一个无限的Stream

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

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

You can use my StreamEx library which enhances standard Stream API. 您可以使用我的StreamEx库来增强标准Stream API。 There is a method pairMap which does exactly what you need: 有一个方法pairMap可以完全满足您的需求:

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

Output: 输出:

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

The pairMap argument is the function which converts the pair of adjacent elements to something which is suitable to your needs. pairMap参数是将相邻元素对转换为适合您需要的元素的函数。 If you have a Pair class in your project, you can use .pairMap(Pair::new) to get the stream of pairs. 如果项目中有Pair类,则可以使用.pairMap(Pair::new)来获取对的流。 If you want to create a stream of two-element lists, you can use: 如果要创建双元素列表流,可以使用:

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]]

This works with any element source (you can use StreamEx.of(collection) , StreamEx.of(stream) and so on), correctly works if you have more stream operations before pairMap and very friendly to parallel processing (unlike solutions which involve stream zipping). 这适用于任何元素源(您可以使用StreamEx.of(collection)StreamEx.of(stream)等),如果您在pairMap之前有更多流操作并且对并行处理非常友好,则正确工作(与涉及流的解决方案不同)荏苒)。

In case if your input is a List with fast random access and you actually want List<List<String>> as a result, there's a shorter and somewhat different way to achieve this in my library using ofSubLists : 如果您的输入是具有快速随机访问权限的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]]

Here behind the scenes input.subList(i, i+2) is called for each input list position, so your data is not copied to the new lists, but subLists are created which refer to the original list. 在幕后input.subList(i, i+2)为每个输入列表位置调用input.subList(i, i+2) ,因此不会将数据复制到新列表,而是创建引用原始列表的子列表。

Here's a minimal amount of code that creates a List<List<String>> of the pairs: 这是创建对的List<List<String>>的最少量代码:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM