是否有一些仅流的方法来确定最大流元素的索引?

问题描述 投票:0回答:5

我有一个

Stream<Set<Integer>> intSetStream

我可以做到这一点...

Set<Integer> theSetWithTheMax = intSetStream.max( (x,y)->{ return Integer.compare( x.size(), y.size() ); } ).get( );

...我得到了其中

Set<Integer>
元素数量最多的
Integer

那太好了。但我真正需要知道的是,它是最大的

Set
中的第一个
Stream
吗?还是
Set
中的第10个
Stream
?还是第
i
Set
?其中哪一个的元素最多?

所以我的问题是:是否有某种方法 - 使用 Stream API - 我可以确定“

i
Set
中的第
Stream
Set
”返回了最大值他们所有人,为了
Set.size( )
电话
”?

我能想到的最好的解决方案是迭代

Stream<Set<Integer>>
(使用
intSetStream.iterator()
)并进行手动
max( )
计算。但我希望能学到一种更
Stream
-y 的方法;如果有这样的事的话。

java-8 java-stream
5个回答
5
投票

您可以使用自定义收集器来执行此操作:

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];

这是最轻量级的解决方案。当我们使用专用类而不是数组时,它的逻辑变得更加清晰:

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;

状态对象保存大小,它只是到目前为止遇到的元素数量、最后遇到的最大值及其位置,如果当前元素大于最大值,我们将其更新为先前的大小值。这就是 accumulator 函数(

collect
的第二个参数)的作用。

为了支持任意评估顺序,即并行流,我们必须提供一个 combiner 函数(

collect
的最后一个参数)。它将两个部分评估的状态合并到第一个状态。如果第二个状态的最大值更大,我们更新第一个状态的最大值和位置,而我们必须将第一个状态的大小添加到第二个状态的位置以反映两者都是部分结果的事实。此外,我们必须将大小更新为两个大小的总和。


2
投票

一种方法是首先将

Stream<Set<Integer>>
映射到
Collection<Integer>
,其中每个元素是每个
Set<Integer>
的大小,然后您可以提取给定
Stream<Set<Integer>>
的最大元素数,然后得到“通过查找尺寸集合中最大数字的索引来找到该集合的索引。

考虑以下示例:

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

示例输出可能如下所示:

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

1
投票

Streams 方法并非旨在了解当前迭代的元素。
所以我认为你的实际方法是:找到具有最大元素的集合,然后迭代集合以找到该集合也不错。

作为替代方案,您可以首先将

Stream<Set<Integer>>
收集到列表中(以便有办法检索索引)并使用
SimpleImmutableEntry
但这似乎确实有点过分了:

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

1
投票

@Holzer 的基于

Collector
的自定义解决方案中提供的见解(在我彻头彻尾无耻地抄袭IntSummaryStatistics.java的源代码之上),启发了我自己的基于
Collector
的自定义解决方案; 这可能反过来会激励他人...

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

...使用自定义

Collector
,我可以获取 OQ 的
intSetStream
并确定包含最多元素数量的
Set<Integer>
的索引,如下所示...

int indexOfMax = intSetStream.map( Set::size )
    .collect( IndexOfMaxCollector::new,
              IndexOfMaxCollector::accept,  
              IndexOfMaxCollector::combine )
    .getIndexOfMax( );

这个解决方案——诚然不是最“美丽”的——在可重用性可理解性方面可能比其他解决方案有一点点优势。


0
投票

这是使用 StreamEx 库的解决方案:

int index = StreamEx.of(intSetStream)
        .zipWith(IntStream.iterate(0, i -> i + 1))
        .maxBy(e -> e.getKey().size())
        .orElseThrow()
        .getValue();

这使用

StreamEx.zipWith
来压缩具有无限流 [0, 1, 2, 3, …] 的流,生成一个
Stream<Map.Entry<Set<Integer>, Integer>>
,其中
Map.Entry
由流元素及其索引组成。然后,它使用
maxBy
方法来查找具有最大值的
Map.Entry
作为
Optional<Map.Entry>
。最后,它返回可选项中条目的值,即最大条目的索引。

© www.soinside.com 2019 - 2024. All rights reserved.