2

I would like to compare to streams, and check if they have 1 or more elements in common (finding 1 is sufficient to stop looking for more). I want to be able to apply this to Streams containing a custom-created class.

For illustration, let's say I have a class that looks like:

public class Point {
    public final int row;
    public final int col;

    public Point(int row, int col) {
        this.row = row;
        this.col = col;
    }    

    @Override
    public boolean equals(Object obj) {
        if (obj == null) return false;
        if (obj.getClass() != this.getClass()) return false;
        final Point other = (Point) obj;
        return this.row == other.row && this.col == other.col;
    }

    @Override
    public int hashCode() {
        return Objects.hash(row, col); 
    }
}

And then I have two lovely streams that look like:

Stream<Point> streamA = Stream.of(new Point(2, 5), new Point(3, 1));
Stream<Point> streamB = Stream.of(new Point(7, 3), new Point(3, 1));

Given that these Streams have 1 Point in common (namely, Point(3, 1)), I would want the final result to be true.

The desired functionality can be pictured as:

public static boolean haveSomethingInCommon(Stream<Point> a, Stream<Point> b){
    //Code that compares a and b and returns true if they have at least 1 element in common
}
alt-f4
  • 2,112
  • 17
  • 49
  • An important question, this might be - *Could the same element be present in the same stream*? – Naman Nov 23 '20 at 09:19

3 Answers3

3

Without collecting the two streams independently, you can group and identify if multiple values are mapped to any key.

public static boolean haveSomethingInCommon(Stream<Coord> a, Stream<Coord> b) {
    return Stream.concat(a, b)
            .collect(Collectors.groupingBy(Function.identity()))
            .values().stream()
            .anyMatch(l -> l.size() > 1);
}

If the same stream can have the same element twice or more, you can change the code to use -

Stream.concat(a.distinct(), b.distinct())
Naman
  • 27,789
  • 26
  • 218
  • 353
  • Optimization - You can get done with `counting` itself in grouping downstream as mentioned in an [answer by Tagir](https://stackoverflow.com/a/31074919/1746118). – Naman Nov 23 '20 at 09:54
  • 1
    the obvious "problem" here is that this is not short-circuiting, no? – Eugene Nov 23 '20 at 12:23
  • 4
    Mind that streams created by a `Set` and such alike do already know that they are distinct so `distinct()` would be a no-op then. So you could apply it prematurely w/o giving anything up. But looking fluent does not help the performance when the hidden costs are on par with collecting into an ordinary collection. In the end, `distinct()` on a stream that isn’t already distinct will build a `Set` of all encountered values and `groupingBy` fills a `Map` with all values. So `Set> set = a.collect(toSet()); return b.anyMatch(set::contains);` is simpler and more efficient (short-circuiting on `b`) – Holger Nov 23 '20 at 12:25
1

First of all you have to convert your Streams to a Set or List to not get the famous error:

java.lang.IllegalStateException: stream has already been operated upon or closed

And then you can use anyMatch as this:

public static boolean haveSomethingInCommon(Stream<Coord> a, Stream<Coord> b) {
    Set<Coord> setA = a.collect(Collectors.toSet());
    Set<Coord> setB = b.collect(Collectors.toSet());

    return setA.stream().anyMatch(setB::contains);
}

Or you can convert only the b Stream to a Set and use:

public static boolean haveSomethingInCommon(Stream<Coord> a, Stream<Coord> b) {
    Set<Coord> setB = b.collect(Collectors.toSet());
    return a.anyMatch(setB::contains);
}

I would recommend to Set<Coord> instead of Stream<Coord> as param in your method.

public static boolean haveSomethingInCommon(Set<Coord> a, Set<Coord> b) {
    return a.stream().anyMatch(b::contains);
}
Youcef LAIDANI
  • 55,661
  • 15
  • 90
  • 140
  • Would it be more efficient to convert `listB` to a `Set`? – Glains Nov 23 '20 at 07:43
  • @Glains Yes I agree – Youcef LAIDANI Nov 23 '20 at 07:48
  • 1
    Hi @YCF_L : ) isn't it sufficient to only make one of them a List/Set? On a side note, is there actually a difference in performance from using a List vs Set? – alt-f4 Nov 23 '20 at 07:59
  • @alt-f4 Yes you can use `a.anyMatch(b.collect(Collectors.toSet())::contains);` but I'm not sure about the performance of that, about List or Set I would recommand to use Set in your case, because it is better in performance than the List – Youcef LAIDANI Nov 23 '20 at 08:05
  • for the first approach, another [Q&A](https://stackoverflow.com/questions/36670568/comparing-two-collections-using-stream-anymatch) for finding disjoints over collections rather than streams – Naman Nov 23 '20 at 09:28
0

there is a function disjoint in Collections:

public static boolean haveSomethingInCommon( Stream<Coord> a, Stream<Coord> b ) {
  return( ! Collections.disjoint( a.collect( toList() ), b.collect( toList() ) ) );
}
Kaplan
  • 2,572
  • 13
  • 14