可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I have a Stream<Set<Integer>> intSetStream
.
I can do this on it...
Set<Integer> theSetWithTheMax = intSetStream.max( (x,y)->{ return Integer.compare( x.size(), y.size() ); } ).get( );
...and I get a hold of the Set<Integer>
that has the highest number of Integer
elements in it.
That's great. But what I really need to know is, is it the 1st Set
in that Stream
that's the max? Or is it the 10th Set
in the Stream
? Or the i
th Set
? Which one of them has the most elements in it?
So my question is: Is there some way — using the Stream API — that I can determine "It was the i
th Set
in the Stream
of Set
s that returned the largest value of them all, for the Set.size( )
call"?
The best solution I can think of, is to iterate over the Stream<Set<Integer>>
(using intSetStream.iterator()
) and do a hand-rolled max( )
calculation. But I'm hoping to learn a more Stream
-y way to go about it; if there is such a thing.
回答1:
One way to do it is to firstly map Stream<Set<Integer>>
to a Collection<Integer>
where each element is the size of each Set<Integer>
and then you can extract what is the largest number of elements given Stream<Set<Integer>>
and then get the "index" of this set by finding an index of the largest number in the collection of sizes.
Consider following example:
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class IntSetStreamExample {
public static void main(String[] args) {
final Stream<Set<Integer>> stream = Stream.of(
new HashSet<>(Arrays.asList(1,2,3)),
new HashSet<>(Arrays.asList(1,2)),
new HashSet<>(Arrays.asList(1,2,3,4,5)),
new HashSet<>(Arrays.asList(0)),
new HashSet<>(Arrays.asList(0,1,2,3,4,5)),
new HashSet<>()
);
final List<Integer> result = stream.map(Set::size).collect(Collectors.toList());
System.out.println("List of number of elements in Stream<Set<Integer>>: " + result);
final int max = Collections.max(result);
System.out.println("Largest set contains " + max + " elements");
final int index = result.indexOf(max);
System.out.println("Index of the largest set: " + index);
}
}
The exemplary output may look like this:
List of number of elements in Stream<Set<Integer>>: [3, 2, 5, 1, 6, 0]
Largest set contains 6 elements
Index of the largest set: 4
回答2:
You can do this with a custom collector:
int posOfMax = stream.mapToInt(Set::size)
.collect(() -> new int[] { 0, -1, -1 },
(a,i) -> { int pos = a[0]++; if(i>a[2]) { a[1] = pos; a[2] = i; } },
(a1,a2) -> {
if(a2[2] > a1[2]) { a1[1] = a1[0]+a2[1]; a1[2] = a2[2]; }
a1[0] += a2[0];
})[1];
This is the most lightweight solution. Its logic becomes clearer when we use a dedicated class instead of an array:
int posOfMax = stream.mapToInt(Set::size)
.collect(() -> new Object() { int size = 0, pos = -1, max = -1; },
(o,i) -> { int pos = o.size++; if(i>o.max) { o.pos = pos; o.max = i; } },
(a,b) -> {
if(b.max > a.max) { a.pos = a.size+b.pos; a.max = b.max; }
a.size += b.size;
}).pos;
The state object holds the size, which is simply the number of elements encountered so far, the last encountered max value and its position which we update to the previous value of the size if the current element is bigger than the max value. That’s what the accumulator function (the second argument to collect
) does.
In order to support arbitrary evaluation orders, i.e. parallel stream, we have to provide a combiner function (the last argument to collect
). It merges the state of two partial evaluation into the first state. If the second state’s max value is bigger, we update the first’s max value and the position, whereas we have to add the first state’s size to the second’s position to reflect the fact that both are partial results. Further, we have to update the size to the sum of both sizes.
回答3:
Streams methods are not designed to be aware of the current element iterated.
So I think that you actual way : find the Set with the max of elements and then iterate on the Sets to find this Set is not bad.
As alternative you could first collect the Stream<Set<Integer>>
into a List (to have a way to retrieve the index) and use a SimpleImmutableEntry
but it seems really overkill :
Stream<Set<Integer>> intSetStream = ...;
List<Set<Integer>> list = intSetStream.collect(Collectors.toList());
SimpleImmutableEntry<Integer, Set<Integer>> entry =
IntStream.range(0, list.size())
.mapToObj(i -> new SimpleImmutableEntry<>(i, list.get(i)))
.max((x, y) -> {
return Integer.compare(x.getValue()
.size(),
y.getValue()
.size());
})
.get();
Integer index = entry.getKey();
Set<Integer> setWithMaxNbElements = entry.getValue();
回答4:
Insight provided in @Holzer's custom Collector
-based solution (on top of my downright shameless plagiarizing of the source code of IntSummaryStatistics.java), inspired a custom Collector
-based solution of my own; that might, in turn, inspire others...
public class IndexOfMaxCollector implements IntConsumer {
private int max = Integer.MIN_VALUE;
private int maxIdx = -1;
private int currIdx = 0;
public void accept( int value ){
if( value > max )
maxIdx = currIdx;
max = Math.max( max, value );
currIdx++;
}
public void combine( IndexOfMaxCollector other ){
if( other.max > max ){
maxIdx = other.maxIdx + currIdx;
max = other.max;
}
currIdx += other.currIdx;
}
public int getMax( ){ return this.max; }
public int getIndexOfMax( ){ return this.maxIdx; }
}
...Using that custom Collector
, I could take the intSetStream
of my OQ and determine the index of the Set<Integer>
that contains the highest number of elements, like this...
int indexOfMax = intSetStream.map( Set::size )
.collect( IndexOfMaxCollector::new,
IndexOfMaxCollector::accept,
IndexOfMaxCollector::combine )
.getIndexOfMax( );
This solution — admittedly not the most "beautiful" — possibly has a teensie bit of an edge over others in both the reusability and understandability stakes.