将一个集合划分为 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
执行此操作不同,可以保证true
和false
列表都将出现在地图中,即使它们是空的。 (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<Boolean, List<Integer>> map = Arrays.stream(test).boxed().collect(Collectors.groupingBy(x -> (x & 1) == 0));
以上是关于将一个集合划分为 2 个不同集合的最佳方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章