将一个集合划分为 2 个不同集合的最佳方法是啥?

Posted

技术标签:

【中文标题】将一个集合划分为 2 个不同集合的最佳方法是啥?【英文标题】:What is the best way to divide a collection into 2 different collections?将一个集合划分为 2 个不同集合的最佳方法是什么? 【发布时间】:2018-07-16 19:46:31 【问题描述】:

我有一组数字:

 Set<Integer> mySet = [ 1,2,3,4,5,6,7,8,9]

我想把它分成两组赔率和偶数。

我的方法是使用两次过滤器:

Set<Integer> set1 = mySet.stream().filter(y -> y % 2 == 0).collect(Collectors.toSet())
Set<Integer> set2 =mySet.stream().filter(y -> y % 2 != 0).collect(Collectors.toSet())

我不喜欢这个解决方案,因为我把整个系列都检查了两次。

有什么更聪明的方法吗?

【问题讨论】:

只需迭代元素,检查它们是偶数还是奇数,然后将它们添加到适当的集合中。一次迭代。 您不想使用经典的 for 循环和 if/else 语句吗?做你的要求很容易...... 也许使用 .map 而不是 .filter 在拆分之前对列表进行排序...并查看性能...这样说是因为您已经接受了答案...也试试这个 @Pras 性能会更差。排序将元素个数为 O(n) 的算法变成 O(n log n)。 【参考方案1】:
Map<Boolean, List<Integer>> partitioned = 
    set.stream().collect(Collectors.partitioningBy(x -> x%2 == 0));

partitioned.get(true) 中的元素是偶数; partitioned.get(false) 中的元素是奇数。

与使用groupingBy 执行此操作不同,可以保证truefalse 列表都将出现在地图中,即使它们是空的。 (Java 8 中没有记录,但确实如此;Java 9 的文档现在明确说明了这一点)。

【讨论】:

这与 OP 所拥有的本质上不是一样的吗……至少要遍历整个系列两次?不同之处在于 OP 现在有两个不需要再次处理的列表,您的方法将需要在每次有人想要偶数或赔率时处理该列表。 @JeffC,据我了解 OP 的问题,OP 关心过滤的两个调用,因为这两个调用都将遍历整个集合。 Andy 的答案只会对集合进行一次迭代,将其分成两组。 @conman124 如果我调用partitioned.get(true) 然后partitioned.get(false) 来获取这两个子集,那么集合会被迭代两次,对吧? @JeffC 不,collect 调用会将原始集合拆分为两个列表,一个“真”和一个“假”。真表是偶数元素,假表是奇数元素。调用 partitioned.get(true) 只会返回由 collect 创建的“真实”列表 @JeffC conman124 说了什么。地图是常规的HashMap:它不是集合中元素的视图。【参考方案2】:

您可以使用Collectors#partitioningBy,如下所示。

Map<Boolean,List<Integer>> evenOddMap  = mySet.stream().collect(Collectors.partitioningBy(e -> e % 2 == 0));
System.out.println("Even : "+evenOddMap.get(true));
System.out.println("Odd : "+evenOddMap.get(false));

【讨论】:

【参考方案3】:

简单的循环和 if/else 将是一个干净简单的解决方案

Set<Integer> setEven = new HashSet<>();
Set<Integer> setOdd = new HashSet<>();

for (Integer val : mySet) 
    if (val % 2 == 0)
        setEven.add(val);
    else
        setOdd.add(val);

或者使用ternary operator 可以更好地简化代码

for(Integer val : mySet) 
    ((val % 2 == 0) ? setEven : setOdd).add(val);

【讨论】:

-1 以这种方式使用条件表达式(但实际上并不是投票):您必须引入一个无意义的变量,因为它不应该像这样使用。只需坚持 if/else。如果您真的想要使用条件表达式,请执行((val%2 == 0) ? setEven : setOdd).add(val); 第二个变体中的boolean b 似乎已过时。您可以忽略未使用的值。 @AndyTurner 感谢您的建议,我会做出改变【参考方案4】:

你可以使用Collectors.partitioningBy:

        Map< Boolean, Set<Integer> > map =
        mySet.stream().collect( Collectors.partitioningBy( y -> y % 2 == 0, 
        Collectors.toSet() ) );

        Set<Integer> odds = map.get(Boolean.TRUE);
        Set<Integer> evens = map.get(Boolean.FALSE);

编辑:

我看到有几个类似的答案。这里的细微差别在于它显示了如何将集合作为 Set 而不是 List 以防 OP 想要这样。

【讨论】:

【参考方案5】:

如果你已经有集合来保存值,下面可以是一个解决方案。

data.stream().forEach(x -> 
if(x%2==0)
//add to collection holding even nums
 else 
//add to collection holding odd nums

)

【讨论】:

【参考方案6】:

您可以使用groupingBy

public void test(String[] args) 
    Integer[] test = 1,2,3,4,5,6,7,8,9;
    Map<Integer, List<Integer>> evenOdd = Arrays.stream(test).collect(Collectors.groupingBy(i -> i & 1));
    Set<Integer> evens = new HashSet<>(evenOdd.get(0));
    Set<Integer> odds = new HashSet<>(evenOdd.get(1));
    System.out.println("Evens "+evens+" Odds "+odds);

【讨论】:

groupingBy 的缺点是它不能保证“奇数”和“偶数”列表都存在。您可以使用evenOdd.getOrDefault(0, Collections.emptyList());但是使用partitioningBy 会更容易。 (不是 DV)您可以在收集的同时使用下游收集器转换为集合:.groupingBy(predicate, Collectors.toSet()) @OldCurmudgeon 无法说明为什么投反对票,如果您知道两者都会被填充,或者您可能希望从地图中获取 null(如果它们丢失)。好吧,+1,只是你可以稍微简化一下Map&lt;Boolean, List&lt;Integer&gt;&gt; map = Arrays.stream(test).boxed().collect(Collectors.groupingBy(x -&gt; (x &amp; 1) == 0));

以上是关于将一个集合划分为 2 个不同集合的最佳方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

集合划分问题算法探讨

算法设计与分析: 2-8 集合划分问题

算法设计与分析: 2-8 集合划分问题

将信息从一个集合视图传递到另一个更详细的集合视图的最佳方法是啥

机器学习——模型评估与选择

698. 划分为k个相等的子集(Python)