35

I have a Set of numbers :

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

I want to divide it into 2 sets of odds and evens.

My way was to use filter twice :

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

I don't like this solution because I go over the whole set twice.

Is there any smarter way to do it?

user1386966
  • 2,962
  • 11
  • 33
  • 63
  • 3
    Just iterate the elements, check if they're even or odd, and add them to the appropriate set. One iteration. – Kon Feb 06 '18 at 17:04
  • 1
    do you not want to use a classic for loop and if/else statement? it's pretty easy to do what your asking... – RAZ_Muh_Taz Feb 06 '18 at 17:05
  • Maybe use .map instead of .filter – Stucco Feb 06 '18 at 17:06
  • Sort the list before splitting... And see the performance... Saying this because you have an already accepted answer... Just try this out too – Pras Feb 07 '18 at 16:32
  • @Pras the performance would be worse. Sorting turns an algorithm which is O(n) in the number of elements into O(n log n). – Andy Turner Feb 09 '18 at 14:19

6 Answers6

40
Map<Boolean, List<Integer>> partitioned = 
    set.stream().collect(Collectors.partitioningBy(x -> x%2 == 0));

The elements in partitioned.get(true) are even; the elements in partitioned.get(false) are odd.

Unlike doing this using groupingBy, it is guaranteed that both true and false lists will be present in the map even if they are empty. (Not documented in Java 8, but it was true; Java 9's doc now states it explicitly).

Andy Turner
  • 122,430
  • 10
  • 138
  • 216
  • Isn't this essentially the same as what OP had... at least as far as going over the whole set twice? The difference being the OP now has two lists that don't need to be processed again where your approach will necessitate processing the list each time someone wants evens or odds. – JeffC Feb 06 '18 at 20:10
  • 3
    @JeffC, as far as I understand OPs quesion, OP is concerned about the two calls to filter since both calls will iterate over the entire set. Andy's answer will only iterate over the set once, partitioning it into two groups. – conman124 Feb 06 '18 at 20:36
  • @conman124 If I call `partitioned.get(true)` and then `partitioned.get(false)` to get the two subsets, the set gets iterated over twice, right? – JeffC Feb 06 '18 at 20:38
  • 4
    @JeffC no, the `collect` call will split the original set into two lists, one "true" and one "false". The true list is the even elements, and the false list is the odd elements. Calling `partitioned.get(true)` just returns the "true" list that was created by `collect` – conman124 Feb 06 '18 at 20:41
  • @JeffC what conman124 said. The map is a regular `HashMap`: it's not a view of the elements in the set. – Andy Turner Feb 06 '18 at 21:17
13

Simple loop and if/else would be a clean and simple solution

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

Or using a ternary operator works well to simplify the code even more

for(Integer val : mySet) {
    ((val % 2 == 0) ? setEven : setOdd).add(val);
}
RAZ_Muh_Taz
  • 3,964
  • 1
  • 10
  • 22
  • 9
    -1 to using the conditional expression in that way (not actually downvoting, though): you have to introduce a meaningless variable, because it's not meant to be used like this. Just stick with the if/else. If you *really* want to use a conditional expression, do `((val%2 == 0) ? setEven : setOdd).add(val);`. – Andy Turner Feb 06 '18 at 21:28
  • The `boolean b` in the second variant seems to be obsolete. You could just ignore the unused value. – gronostaj Feb 07 '18 at 11:34
  • 1
    @AndyTurner thanks for the suggestion, i'll make the changes – RAZ_Muh_Taz Feb 07 '18 at 16:29
13

You can use Collectors#partitioningBy like below.

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));
Amit Bera
  • 6,464
  • 1
  • 13
  • 37
11

You can use 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);

EDIT:

I see there are a couple of similar answers. The slight difference here is that it shows how to get the collections as Set instead of List in case OP wanted it that way.

tsolakp
  • 5,560
  • 1
  • 17
  • 22
1

Provided you already have collections to hold values, the below can be a solution.

data.stream().forEach(x -> {
if(x%2==0){
//add to collection holding even nums
} else {
//add to collection holding odd nums
}
})
Amar Dev
  • 1,172
  • 2
  • 14
  • 31
0

You can use a 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);
}
OldCurmudgeon
  • 60,862
  • 15
  • 108
  • 197
  • 2
    The disadvantage of `groupingBy` is that it doesn't guarantee both "odd" and "even" lists to be present. You can use `evenOdd.getOrDefault(0, Collections.emptyList())`; but it's easier just to use `partitioningBy`. – Andy Turner Feb 06 '18 at 17:16
  • 2
    (Not the DV) you could do the conversion to sets at the same time as collecting, using a downstream collector: `.groupingBy(predicate, Collectors.toSet())`. – Andy Turner Feb 06 '18 at 17:30
  • @OldCurmudgeon can't tell why the down vote, if you know that both will be populated, or may be you might want to get a `null` from the map if they are missing. Well, +1, just that you can simplify this a little probably `Map> map = Arrays.stream(test).boxed().collect(Collectors.groupingBy(x -> (x & 1) == 0));` – Eugene Feb 06 '18 at 19:48