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

时间:2015-08-23 01:58:41

标签: java lambda java-8 java-stream

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

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

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

这与Zipping几乎相同,如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);});

3 个答案:

答案 0 :(得分:3)

这应该做你想要的,基于@ njzk2的两次使用流的注释,跳过第二种情况下的第一个元素。它使用您在原始问题中链接的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中没有“相邻”元素的概念。有good discussion on this here

我可以想到两种方法,但我不认为你会喜欢它们中的任何一种:

  1. Stream转换为List,然后执行上面的解决方案。很丑,但只要Stream不是无限的就行,但性能并不重要。
  2. 使用@TagirValeev's answer below,只要您使用StreamEx而不是Stream,并且愿意在第三方库中添加依赖项。
  3. 此讨论也与此问题相关:Can I duplicate a Stream in Java 8?;这对你的问题不是好消息,但值得一读,可能会有一个更吸引你的解决方案。

答案 1 :(得分:3)

如果你:

  1. 不想创建包含流媒体中所有字符串的列表
  2. 不想使用外部库
  3. 喜欢弄脏你的手
  4. 然后,您可以创建一个方法,使用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());
        }
    }
    

    方法和参数命名的灵感来自他们的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]

答案 2 :(得分:2)

您可以使用我的StreamEx库来增强标准Stream API。有一种方法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个类,则可以使用.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>>,那么在我的库中使用{实现此目的的方式更短且有所不同{3}}:

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),因此您的数据不会复制到新列表,但会创建引用原始列表的子列表。