람다와 함께 JDK8을 사용하여 압축 스트림 (java.util.stream.Streams.zip)


149

람다 b93이있는 JDK 8에는 스트림을 압축하는 데 사용할 수 있는 클래스 java.util.stream.Streams.zip이 b93 에 있습니다 ( Dhananjay Nene의 Java8 Lambdas 탐험 1 부 튜토리얼에 설명되어 있음 ). 이 기능 :

요소가 두 스트림의 요소를 결합한 결과 인 지연 및 순차 결합 스트림을 작성합니다.

그러나 b98에서 이것은 사라졌습니다. 실제로 Streams클래스는 b98의 java.util.stream 에서도 액세스 할 수 없습니다 .

이 기능이 이동 되었습니까? 그렇다면 b98을 사용하여 스트림을 간결하게 압축하는 방법은 무엇입니까?

내가 생각한 응용 프로그램 은 Shen 의이 Java 구현에 있으며 , 여기서 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의 기능을 사용하지 않음).


3
아, 방금 완전히 제거 된 것 같습니다 : mail.openjdk.java.net/pipermail/lambda-libs-spec-observers/…
artella

"Java8 람다 1 부 탐구."-이 기사에 대한 새로운 링크 것은 blog.dhananjaynene.com/2013/02/exploring-java8-lambdas-part-1
알렉세이 에고 로프

답변:


77

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

1
결과 스트림은 있어야하지 않나요 SIZED경우 중 하나 스트림은 SIZED, 둘 다하지?
Didier L

5
나는 그렇게 생각하지 않습니다. SIZED이 구현이 작동 하려면 두 스트림 모두 있어야합니다 . 실제로 압축을 정의하는 방법에 따라 다릅니다. 예를 들어 크기가 다른 두 개의 스트림을 압축 할 수 있어야합니까? 결과 스트림은 어떻게 생겼습니까? 이것이 이것이이 기능이 실제로 API에서 생략 된 이유라고 생각합니다. 이를 수행하는 방법은 여러 가지가 있으며 사용자는 "올바른"동작을 결정해야합니다. 더 긴 스트림에서 요소를 삭제하거나 더 짧은 목록을 채우시겠습니까? 그렇다면 어떤 가치로?
시키

내가 빠진 것이 아니라면 캐스트가 필요하지 않습니다 Spliterator<A>.
jub0bs

Java 8 b93 소스 코드가 호스팅되는 웹 사이트가 있습니까? 찾는 데 문제가 있습니다.
Starwarswii

42

zip은 protonpack 라이브러리에서 제공하는 기능 중 하나입니다 .

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


34

프로젝트에 Guava가있는 경우 Streams.zip 메소드를 사용할 수 있습니다 (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) {
         ...
     }
 }

26

lambda ( gist ) 와 함께 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);
}

2
좋은 솔루션과 (상대적으로) 컴팩트! 당신이 두는 것이 필요 import java.util.function.*;하고 import java.util.stream.*;파일의 상단에.
sffc

이것은 스트림에서의 터미널 조작입니다. 이는 무한 스트림의 경우이 방법이 다음과 같이 분류됨을 의미합니다.
smac89

2
너무 쓸모없는 래퍼 : 여기 () -> iterator그리고 여기 다시 : iterable.spliterator(). 왜 직접 구현하기 Spliterator보다는 직접 구현하지 Iterator않겠습니까? @Doradus 응답 확인 stackoverflow.com/a/46230233/1140754을
미구엘 감 보아

20

인덱스가 아닌 컬렉션 (목록) 이외의 컬렉션에서 압축을 사용할 수 없으므로 단순성에 대한 열렬한 팬이므로 이것이 내 해결책이 될 것입니다.

<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).mapToObj( i -> {
          return zipper.apply(lista.get(i), listb.get(i));
     });        
}

1
나는 mapToObject해야 한다고 생각 합니다 mapToObj.
seanf

목록이 없으면 RandomAccess(예 : 링크 된 목록) 매우 느릴 것입니다
avmohan

명확히. 그러나 대부분의 Java 개발자는 LinkedList의 인덱스 액세스 조작 성능이 좋지 않다는 것을 잘 알고 있습니다.
라파엘

11

언급 한 클래스의 메소드 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);
}

predicate당신은 필터 상태 저장에 전달 되지 않습니까? 이는 메소드 계약을 위반하며 스트림을 병렬로 처리 할 때 특히 작동하지 않습니다.
Andreas

2
@Andreas : 병렬 처리를 지원하는 솔루션은 없습니다. 내 메소드는 스트림을 반환하지 않기 때문에 스트림이 병렬로 실행되지 않도록합니다. 마찬가지로, 허용 된 답변의 코드는 병렬로 전환 할 수 있지만 실제로는 병렬로 아무것도하지 않는 스트림을 반환합니다. 즉, Statefull 술어는 권장되지 않지만 계약을 위반하지는 않습니다. 상태 업데이트가 스레드로부터 안전하다는 것을 보장하는 경우 병렬 컨텍스트에서도 사용될 수 있습니다. 어떤 상황에서는 피할 수없는 경우가 있습니다. 예를 들어 스트림을 별개의 것으로 바꾸는 것은 상태 전체 술어 그 자체 입니다.
Holger

2
@Andreas : Java API에서 이러한 작업이 제거 된 이유를 추측 할 수 있습니다.
Holger

8

겸손하게이 구현을 제안합니다. 결과 스트림은 두 입력 스트림 중 짧은 스트림으로 잘립니다.

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

나는 당신의 제안을 좋아합니다. 그러나 나는 last에 완전히 동의하지 않습니다 .., leftStream.isParallel() || rightStream.isParallel(). AbstractSpliterator기본적으로 병렬 처리가 제한되어 있기 때문에 효과가 없다고 생각합니다 . 따라서 최종 결과는 통과와 동일하다고 생각합니다 false.
Miguel Gamboa

@MiguelGamboa-귀하의 의견에 감사드립니다. "기본적으로 병렬 처리 제한"이라는 의미가 무엇인지 잘 모르겠습니다. 일부 문서에 대한 링크가 있습니까?
Doradus

6

Lazy-Seq 라이브러리는 zip 기능을 제공합니다.

https://github.com/nurkiewicz/LazySeq

이 라이브러리는 많은 영감을 받았으며 scala.collection.immutable.Stream변경 불가능하고 스레드로부터 안전하며 사용하기 쉬운 게으른 시퀀스 구현, 무한한 가능성을 제공하는 것을 목표로합니다.


5

최신 구아바 라이브러리 ( Streams클래스 용)를 사용하면 할 수 있어야합니다

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

2

이것이 당신을 위해 작동합니까? 압축 기능이있는 스트림을 느리게 평가하는 짧은 기능이므로 무한 스트림을 제공 할 수 있습니다 (압축되는 스트림의 크기를 취할 필요는 없음).

스트림이 유한 한 경우 스트림 중 하나에 요소가 부족 해지면 중지됩니다.

import java.util.Objects;
import java.util.function.BiFunction;
import java.util.stream.Stream;

class StreamUtils {
    static <ARG1, ARG2, RESULT> Stream<RESULT> zip(
            Stream<ARG1> s1,
            Stream<ARG2> s2,
            BiFunction<ARG1, ARG2, RESULT> combiner) {
        final var i2 = s2.iterator();
        return s1.map(x1 -> i2.hasNext() ? combiner.apply(x1, i2.next()) : null)
                .takeWhile(Objects::nonNull);
    }
}

다음은 몇 가지 단위 테스트 코드입니다 (코드 자체보다 훨씬 깁니다).

import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static org.junit.jupiter.api.Assertions.assertEquals;

class StreamUtilsTest {
    @ParameterizedTest
    @MethodSource("shouldZipTestCases")
    <ARG1, ARG2, RESULT>
    void shouldZip(
            String testName,
            Stream<ARG1> s1,
            Stream<ARG2> s2,
            BiFunction<ARG1, ARG2, RESULT> combiner,
            Stream<RESULT> expected) {
        var actual = StreamUtils.zip(s1, s2, combiner);

        assertEquals(
                expected.collect(Collectors.toList()),
                actual.collect(Collectors.toList()),
                testName);
    }

    private static Stream<Arguments> shouldZipTestCases() {
        return Stream.of(
                Arguments.of(
                        "Two empty streams",
                        Stream.empty(),
                        Stream.empty(),
                        (BiFunction<Object, Object, Object>) StreamUtilsTest::combine,
                        Stream.empty()),
                Arguments.of(
                        "One singleton and one empty stream",
                        Stream.of(1),
                        Stream.empty(),
                        (BiFunction<Object, Object, Object>) StreamUtilsTest::combine,
                        Stream.empty()),
                Arguments.of(
                        "One empty and one singleton stream",
                        Stream.empty(),
                        Stream.of(1),
                        (BiFunction<Object, Object, Object>) StreamUtilsTest::combine,
                        Stream.empty()),
                Arguments.of(
                        "Two singleton streams",
                        Stream.of("blah"),
                        Stream.of(1),
                        (BiFunction<Object, Object, Object>) StreamUtilsTest::combine,
                        Stream.of(pair("blah", 1))),
                Arguments.of(
                        "One singleton, one multiple stream",
                        Stream.of("blob"),
                        Stream.of(2, 3),
                        (BiFunction<Object, Object, Object>) StreamUtilsTest::combine,
                        Stream.of(pair("blob", 2))),
                Arguments.of(
                        "One multiple, one singleton stream",
                        Stream.of("foo", "bar"),
                        Stream.of(4),
                        (BiFunction<Object, Object, Object>) StreamUtilsTest::combine,
                        Stream.of(pair("foo", 4))),
                Arguments.of(
                        "Two multiple streams",
                        Stream.of("nine", "eleven"),
                        Stream.of(10, 12),
                        (BiFunction<Object, Object, Object>) StreamUtilsTest::combine,
                        Stream.of(pair("nine", 10), pair("eleven", 12)))
        );
    }

    private static List<Object> pair(Object o1, Object o2) {
        return List.of(o1, o2);
    }

    static private <T1, T2> List<Object> combine(T1 o1, T2 o2) {
        return List.of(o1, o2);
    }

    @Test
    void shouldLazilyEvaluateInZip() {
        final var a = new AtomicInteger();
        final var b = new AtomicInteger();
        final var zipped = StreamUtils.zip(
                Stream.generate(a::incrementAndGet),
                Stream.generate(b::decrementAndGet),
                (xa, xb) -> xb + 3 * xa);

        assertEquals(0, a.get(), "Should not have evaluated a at start");
        assertEquals(0, b.get(), "Should not have evaluated b at start");

        final var takeTwo = zipped.limit(2);

        assertEquals(0, a.get(), "Should not have evaluated a at take");
        assertEquals(0, b.get(), "Should not have evaluated b at take");

        final var list = takeTwo.collect(Collectors.toList());

        assertEquals(2, a.get(), "Should have evaluated a after collect");
        assertEquals(-2, b.get(), "Should have evaluated b after collect");
        assertEquals(List.of(2, 4), list);
    }
}

나는 드롭했다 takeWhile그 java8에 될 것 같지 않습니다 된 끝에하지만 수신자가 압축 된 스트림은 동일한 크기하지 않을 때 발생하는 널 (null)을 필터링 할 수 있습니다로는 문제가되지 않습니다. 나는이 답변이 구성되고 이해할 수있는 1 번 대답이어야한다고 생각합니다. 다시 한번 감사드립니다.
simbo1905

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

1

내가 기여하는 AOL의 cyclops-react확장 스트림 구현을 통해 반응 스트림 인터페이스 ReactiveSeq를 구현하는 정적 기능과 정적 메소드를 통해 표준 Java 스트림에 거의 동일한 기능을 제공하는 StreamUtils를 통해 압축 기능을 제공합니다.

 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")

0

누구든지 아직이 필요하면 streamex 라이브러리에 StreamEx.zipWith기능이 있습니다.

StreamEx<String> givenNames = StreamEx.of("Leo", "Fyodor")
StreamEx<String> familyNames = StreamEx.of("Tolstoy", "Dostoevsky")
StreamEx<String> fullNames = givenNames.zipWith(familyNames, (gn, fn) -> gn + " " + fn);

fullNames.forEach(System.out::println);  // prints: "Leo Tolstoy\nFyodor Dostoevsky\n"

-1

대단하다. 하나의 스트림이 키이고 다른 스트림이 값인 맵에 두 개의 스트림을 압축해야했습니다.

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 = 당근}

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.