Сжатие потоков с помощью JDK8 с лямбда (java.утиль.поток.Потоки.застежка-молния)


в JDK 8 с лямбда b93 был класс java.утиль.поток.Потоки.zip в b93 который может быть использован для zip потоков (это показано в учебнике Изучение Java8 Лямбды. Часть 1 по Dhananjay Nene). Эта функция :

создает ленивый и последовательный комбинированный поток, элементами которого являются результат объединения элементов двух потоков.

однако в b98 это исчезло. Фактически Streams класс даже не доступен в java.утиль.поток в b98.

была ли перемещена эта функциональность, и если да, то как мне сжато использовать потоки zip с помощью b98?

приложение, которое я имею в виду в этой реализации java Shen, где я заменил функциональность zip в

  • static <T> boolean every(Collection<T> c1, Collection<T> c2, BiPredicate<T, T> pred)
  • static <T> T find(Collection<T> c1, Collection<T> c2, BiPredicate<T, T> pred)

функции с довольно подробным кодом (который не использует функциональность b98).

13 114

13 ответов:

мне это тоже было нужно, поэтому я просто взял исходный код из b93 и поместил его в класс "util". Мне пришлось немного изменить его, чтобы работать с текущим API.

для справки вот рабочий код (возьмите его на свой страх и риск...):

public static<A, B, C> Stream<C> zip(Stream<? extends A> a,
                                     Stream<? extends B> b,
                                     BiFunction<? super A, ? super B, ? extends C> zipper) {
    Objects.requireNonNull(zipper);
    Spliterator<? extends A> aSpliterator = Objects.requireNonNull(a).spliterator();
    Spliterator<? extends B> bSpliterator = Objects.requireNonNull(b).spliterator();

    // Zipping looses DISTINCT and SORTED characteristics
    int characteristics = aSpliterator.characteristics() & bSpliterator.characteristics() &
            ~(Spliterator.DISTINCT | Spliterator.SORTED);

    long zipSize = ((characteristics & Spliterator.SIZED) != 0)
            ? Math.min(aSpliterator.getExactSizeIfKnown(), bSpliterator.getExactSizeIfKnown())
            : -1;

    Iterator<A> aIterator = Spliterators.iterator(aSpliterator);
    Iterator<B> bIterator = Spliterators.iterator(bSpliterator);
    Iterator<C> cIterator = new Iterator<C>() {
        @Override
        public boolean hasNext() {
            return aIterator.hasNext() && bIterator.hasNext();
        }

        @Override
        public C next() {
            return zipper.apply(aIterator.next(), bIterator.next());
        }
    };

    Spliterator<C> split = Spliterators.spliterator(cIterator, zipSize, characteristics);
    return (a.isParallel() || b.isParallel())
           ? StreamSupport.stream(split, true)
           : StreamSupport.stream(split, false);
}

zip-это одна из функций, предоставляемых библиотека протонного пакета.

Stream<String> streamA = Stream.of("A", "B", "C");
Stream<String> streamB  = Stream.of("Apple", "Banana", "Carrot", "Doughnut");

List<String> zipped = StreamUtils.zip(streamA,
                                      streamB,
                                      (a, b) -> a + " is for " + b)
                                 .collect(Collectors.toList());

assertThat(zipped,
           contains("A is for Apple", "B is for Banana", "C is for Carrot"));

Если у вас есть гуаву в вашем проекте, вы можете использовать потоки.молнии метод (был добавлен в Guava 21):

возвращает поток, в котором каждый элемент является результатом передачи соответствующего элемента каждого из streamA и streamB в функцию. Результирующий поток будет таким же длинным, как и более короткий из двух входных потоков; если один поток длиннее, его дополнительные элементы будут проигнорированы. Полученный поток не является эффективно расщепляемым. Это может навредить параллельное исполнение.

 public class Streams {
     ...

     public static <A, B, R> Stream<R> zip(Stream<A> streamA,
             Stream<B> streamB, BiFunction<? super A, ? super B, R> function) {
         ...
     }
 }

сжатие двух потоков с помощью JDK8 с лямбда (суть).

public static <A, B, C> Stream<C> zip(Stream<A> streamA, Stream<B> streamB, BiFunction<A, B, C> zipper) {
    final Iterator<A> iteratorA = streamA.iterator();
    final Iterator<B> iteratorB = streamB.iterator();
    final Iterator<C> iteratorC = new Iterator<C>() {
        @Override
        public boolean hasNext() {
            return iteratorA.hasNext() && iteratorB.hasNext();
        }

        @Override
        public C next() {
            return zipper.apply(iteratorA.next(), iteratorB.next());
        }
    };
    final boolean parallel = streamA.isParallel() || streamB.isParallel();
    return iteratorToFiniteStream(iteratorC, parallel);
}

public static <T> Stream<T> iteratorToFiniteStream(Iterator<T> iterator, boolean parallel) {
    final Iterable<T> iterable = () -> iterator;
    return StreamSupport.stream(iterable.spliterator(), parallel);
}

Так как я не могу представить себе никакого использования молнии на коллекциях, кроме индексированных (списки), и я большой поклонник простоты, это было бы мое решение:

<A,B,C>  Stream<C> zipped(List<A> lista, List<B> listb, BiFunction<A,B,C> zipper){
     int shortestLength = Math.min(lista.size(),listb.size());
     return IntStream.range(0,shortestLength).mapToObject( i -> {
          return zipper.apply(lista.get(i), listb.get(i));
     });        
}

методы класса, который вы упомянули, были перемещены в Stream сам интерфейс в пользу методов по умолчанию. Но кажется, что zip метод был удален. Возможно, потому, что не ясно, каким должно быть поведение по умолчанию для потоков разного размера. Но реализация желаемого поведения прямолинейна:

static <T> boolean every(
  Collection<T> c1, Collection<T> c2, BiPredicate<T, T> pred) {
    Iterator<T> it=c2.iterator();
    return c1.stream().allMatch(x->!it.hasNext()||pred.test(x, it.next()));
}
static <T> T find(Collection<T> c1, Collection<T> c2, BiPredicate<T, T> pred) {
    Iterator<T> it=c2.iterator();
    return c1.stream().filter(x->it.hasNext()&&pred.test(x, it.next()))
      .findFirst().orElse(null);
}

библиотека Lazy-Seq предоставляет функции zip.

https://github.com/nurkiewicz/LazySeq

эта библиотека сильно вдохновлен scala.collection.immutable.Stream и стремится обеспечить неизменяемую, потокобезопасную и простую в использовании реализацию ленивой последовательности, возможно, бесконечную.

public class Tuple<S,T> {
    private final S object1;
    private final T object2;

    public Tuple(S object1, T object2) {
        this.object1 = object1;
        this.object2 = object2;
    }

    public S getObject1() {
        return object1;
    }

    public T getObject2() {
        return object2;
    }
}


public class StreamUtils {

    private StreamUtils() {
    }

    public static <T> Stream<Tuple<Integer,T>> zipWithIndex(Stream<T> stream) {
        Stream<Integer> integerStream = IntStream.range(0, Integer.MAX_VALUE).boxed();
        Iterator<Integer> integerIterator = integerStream.iterator();
        return stream.map(x -> new Tuple<>(integerIterator.next(), x));
    }
}

АОЛ-х Циклоп-реагировать, в который я вношу свой вклад, также обеспечивает функциональность молнии, как через расширенная реализация трансляция, который также реализует интерфейс reactive-streams ReactiveSeq и через StreamUtils, который предлагает большую часть той же функциональности с помощью статических методов для стандартных потоков Java.

 List<Tuple2<Integer,Integer>> list =  ReactiveSeq.of(1,2,3,4,5,6)
                                                  .zip(Stream.of(100,200,300,400));


  List<Tuple2<Integer,Integer>> list = StreamUtils.zip(Stream.of(1,2,3,4,5,6),
                                                  Stream.of(100,200,300,400));

Он также предлагает более обобщенную Аппликативную застежку-молнию. Е. Г.

   ReactiveSeq.of("a","b","c")
              .ap3(this::concat)
              .ap(of("1","2","3"))
              .ap(of(".","?","!"))
              .toList();

   //List("a1.","b2?","c3!");

   private String concat(String a, String b, String c){
    return a+b+c;
   }

и даже возможность сопряжения каждого элемента в одном потоке с каждым элементом в другом

   ReactiveSeq.of("a","b","c")
              .forEach2(str->Stream.of(str+"!","2"), a->b->a+"_"+b);

   //ReactiveSeq("a_a!","a_2","b_b!","b_2","c_c!","c2")

Это здорово. Мне пришлось заархивировать два потока в карту с одним потоком, являющимся ключом, а другим-значением

Stream<String> streamA = Stream.of("A", "B", "C");
Stream<String> streamB  = Stream.of("Apple", "Banana", "Carrot", "Doughnut");    
final Stream<Map.Entry<String, String>> s = StreamUtils.zip(streamA,
                    streamB,
                    (a, b) -> {
                        final Map.Entry<String, String> entry = new AbstractMap.SimpleEntry<String, String>(a, b);
                        return entry;
                    });

System.out.println(s.collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue())));

выход: {A = Яблоко, B=Банан, C=Морковь}

Я смиренно предлагаю эту реализацию. Полученный поток усекается до более короткого из двух входных потоков.

public static <L, R, T> Stream<T> zip(Stream<L> leftStream, Stream<R> rightStream, BiFunction<L, R, T> combiner) {
    Spliterator<L> lefts = leftStream.spliterator();
    Spliterator<R> rights = rightStream.spliterator();
    return StreamSupport.stream(new AbstractSpliterator<T>(Long.min(lefts.estimateSize(), rights.estimateSize()), lefts.characteristics() & rights.characteristics()) {
        @Override
        public boolean tryAdvance(Consumer<? super T> action) {
            return lefts.tryAdvance(left->rights.tryAdvance(right->action.accept(combiner.apply(left, right))));
        }
    }, leftStream.isParallel() || rightStream.isParallel());
}

Если кому-то это еще нужно, есть

использование последней библиотеки Guava (для Streams класс) вы должны быть в состоянии сделать

final Map<String, String> result = 
    Streams.zip(
        collection1.stream(), 
        collection2.stream(), 
        AbstractMap.SimpleEntry::new)
    .collect(Collectors.toMap(e -> e.getKey(), e  -> e.getValue()));