将一个集合拆分成两个不同的集合,最佳方式是什么?

35

我有一组数字:

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

我不喜欢这个解决方案,因为我要两次遍历整个集合。

有没有更聪明的方法?


3
只需迭代元素,检查它们是奇数还是偶数,并将它们添加到相应的集合中。一次迭代即可。 - Kon
1
你不想使用传统的for循环和if/else语句吗?做你要求的事情相当容易... - RAZ_Muh_Taz
也许可以使用 .map 而不是 .filter。 - Stucco
在拆分列表之前对其进行排序... 然后查看性能... 之所以这样说是因为您已经接受了一个答案... 也试试这个。 - Pras
@Pras,性能会变差。排序将一个O(n)算法转换为O(n log n)算法,其中n是元素数量。 - Andy Turner
6个回答

41
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的文档现在明确说明了这一点)。


这不就跟楼主的方法基本一致吗...至少都是遍历整个集合两次吧?不同之处在于,现在楼主有了两个不需要再次处理的列表,而你的方法每次需要处理列表以获取偶数或奇数。 - JeffC
3
据我理解OP的问题,他担心两个filter调用都会遍历整个集合。 Andy的答案只会遍历一次集合,并将其分成两组。 - conman124
如果我调用 partitioned.get(true) 然后再调用 partitioned.get(false) 来获取这两个子集,那么这个集合会被迭代两次,对吗? - JeffC
4
@JeffC 不, collect 方法会将原始集合分成两个列表,一个是“true”,一个是“false”。其中,“true”列表包含所有偶数元素,“false”列表包含所有奇数元素。调用 partitioned.get(true) 只会返回由 collect 创建的“true”列表。 - conman124
@JeffC 就像 conman124 所说的那样。这个 map 是一个普通的 HashMap:它不是集合中元素的视图。 - Andy Turner

13

简单的循环和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);
}

或者使用三元运算符来进一步简化代码也是很有效的。

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

9
对于那种使用条件表达式的方法(虽然我没有实际点踩),我建议不要这样做:因为你必须引入一个无意义的变量,因为它不应该像这样使用。只需坚持使用if/else。如果你真的想使用条件表达式,请使用((val%2 == 0) ? setEven : setOdd).add(val); - Andy Turner
第二个变量中的 boolean b 似乎已经过时了。您可以忽略未使用的值。 - gronostaj
1
@AndyTurner 谢谢您的建议,我会进行更改。 - RAZ_Muh_Taz

13

您可以像以下这样使用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));

11
你可以使用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的形式获取集合,而不是列表,以防OP希望使用该方式。


1

如果您已经有了用于保存值的集合,下面的内容可能是一个解决方案。

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

0
你可以使用 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);
}

2
groupingBy 的缺点是它不能保证同时存在“奇数”和“偶数”列表。你可以使用 evenOdd.getOrDefault(0, Collections.emptyList());但更容易的方法是使用 partitioningBy - Andy Turner
2
你可以在收集数据的同时进行集合转换,使用下游收集器:.groupingBy(predicate, Collectors.toSet()) - Andy Turner
@OldCurmudgeon不知道为什么会被点踩,如果你知道两个都将被填充,或者可能你希望从地图中获取null如果它们丢失了。 好吧,+1,只是你可以简化这个问题,可能Map<Boolean,List<Integer>> map=Arrays.stream(test).boxed().collect(Collectors.groupingBy(x->(x&1)==0)); - Eugene

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接