Google Guava Sets Utility Class

Google Guava Sets Utility Class

The Google Guava Sets utility class has static utility methods pertaining to Set instances. Let us look at the useful methods from the Sets utility class.

Union, Intersection, Difference and Symmetric Difference

Let us look at methods to find the union, intersection, difference and symmetric difference between sets.

Sets#union

The union method takes two sets as arguments and returns the union of the two sets (as an unmodifiable view). The returned set has all the elements from the two sets. When we iterate the result (union), it first iterates over the first set and then over the second set, considering only the elements not present in the first set.

Below code shows an example of finding the union of two sets with and without duplicates.

Set<String> set1 = new HashSet<>(Set.of("a", "b", "c", "d"));
Set<String> set2 = new HashSet<>(Set.of("c", "d", "e", "f"));
Set<String> union = Sets.union(set1, set2);
System.out.println(union); //[a, b, c, d, e, f]


//[a, b, c, d]
System.out.println(
        Sets.union(
                new HashSet<>(Set.of("a", "b")),
                new HashSet<>(Set.of("c", "d"))
        )
);

Sets#intersection

The intersection method takes two sets and returns the intersection of them (an unmodifiable view). In other words, the resultant set has all the elements which are present in both the sets. The iteration order of the resultant set matches that of the first set.

As per the Javadoc, the performance would be better if we pass the smaller set as the first set.

In the first example, the elements [c, d] are the common elements between the two sets. In the second example, there are no common elements and hence it returns an empty set.

Set<String> set1 = new HashSet<>(Set.of("a", "b", "c", "d"));
Set<String> set2 = new HashSet<>(Set.of("c", "d", "e", "f"));
Set<String> intersection = Sets.intersection(set1, set2);
System.out.println(intersection); //[c, d]

//[]
System.out.println(Sets.intersection(
        new HashSet<>(Set.of("a", "b")),
        new HashSet<>(Set.of("c", "d"))
));

Sets#difference

The difference method returns an unmodifiable view of the difference of two sets. The returned set has all the elements present in the first set, but not contained in the second set. Here, the iteration order of the returned set matches that of the first set.

In this example, the elements [a, b] are present in the first set, but not in the second, and hence it is the result of Sets#difference.

Set<String> set1 = new HashSet<>(Set.of("a", "b", "c", "d"));
Set<String> set2 = new HashSet<>(Set.of("c", "d"));
Set<String> difference = Sets.difference(set1, set2);
System.out.println(difference); //[a, b]

Note that the second set may contain elements not present in the first set and these are ignored. In the below example, the elements [e, f] are ignored (present only in the second set).

System.out.println(Sets.difference(
        new HashSet<>(Set.of("a", "b", "c", "d")),
        new HashSet<>(Set.of("c", "d", "e", "f"))
)); //[a, b]

Sets#symmetricDifference

The symmetricDifference takes two sets and returns the elements which are present in either of the two sets, but not in both. The iteration order of the returned set is undefined.

In both the below examples, the elements [c, d] are present in both the sets and hence not part of the result.

Set<String> set1 = new HashSet<>(Set.of("a", "b", "c", "d"));
Set<String> set2 = new HashSet<>(Set.of("c", "d"));
Set<String> symmetricDifference = Sets.symmetricDifference(set1, set2);
System.out.println(symmetricDifference); //[a, b]

//[a, b, e, f]
System.out.println(Sets.symmetricDifference(
        new HashSet<>(Set.of("a", "b", "c", "d")),
        new HashSet<>(Set.of("c", "d", "e", "f"))
));

Sets#complementOf

There are two overloaded complementOf methods. The first method takes a Collection<E> as an argument where the type E is E extends Enum<E>. It creates and returns an EnumSet which has all the enum values that are not present in the passed collection.

Note: If the collection is an EnumSet, this method has the same behavior as EnumSet.complementOf. Otherwise, the specified collection must have at least one element to determine the element type.

We have the Color enum, as shown below.

enum Color {
    RED,
    GREEN,
    BLUE,
    BLACK;
}

We have a collection (list) of colors, as shown below. When we pass it to the Sets#complementOf method, the returned EnumSet has the colors not present in the passed collection.

List<Color> colors = List.of(Color.RED, Color.GREEN);
EnumSet<Color> complement = Sets.complementOf(colors);
System.out.println(complement); //[BLUE, BLACK]

If we pass an empty collection to the above Sets#complementOf method, it will throw an IllegalArgumentException.

//throws java.lang.IllegalArgumentException: collection is empty; use the other version of this method
List<Color> colors = List.of();
System.out.println(Sets.complementOf(colors));    

If the input collection can be empty, we can use the overloaded Sets#complementOf method, where we pass an additional Class<E> argument to denote the class type.

List<Color> colors = List.of();
System.out.println(Sets.complementOf(colors, Color.class)); //[RED, GREEN, BLUE, BLACK]

Sets#cartesianProduct

The cartesianProduct method takes a list of sets (List<? extends Set<? extends B>>) and returns the cartesian product. In other words, it returns all possible lists formed by choosing one element from each of the sets in order.

Note that it doesn’t compute the cartesian products eagerly. It computes them as we iterate over the resultant set.

 Set<List<Object>> cartesianProduct = Sets.cartesianProduct(List.of(
        ImmutableSet.of(1, 2),
        ImmutableSet.of("A", "B", "C")));
System.out.println(cartesianProduct);

The result (cartesian product) of the above is:

[[1, A], [1, B], [1, C], [2, A], [2, B], [2, C]]

If any input set is empty, the Cartesian product will also be empty.

System.out.println(Sets.cartesianProduct(List.of(
       ImmutableSet.of(1, 2),
       ImmutableSet.of(),
       ImmutableSet.of("A", "B", "C")))); //[]

In case the entire input is empty (no sets are provided), the result will have one element which is an empty list.

System.out.println(Sets.cartesianProduct(List.of())); //[[]]

If any of the sets are null or if any of the elements are null, it will throw a NullPointerException. Both the below code will throw a NullPointerException.

System.out.println(Sets.cartesianProduct(List.of(
        ImmutableSet.of(1, 2),
        null)));
System.out.println(Sets.cartesianProduct(List.of(
        ImmutableSet.of(1, 2),
        ImmutableSet.of("A", "B", null))));

There is an overloaded cartesianProduct method which takes a var-args argument (rather than a list).

System.out.println(Sets.cartesianProduct(
        ImmutableSet.of(1, 2),
        ImmutableSet.of("A", "B", "C"))); //[[1, A], [1, B], [1, C], [2, A], [2, B], [2, C]]

Sets#powerSet

The powerSet returns the set of all possible subsets of set. Within a subset, the elements appear in the same iteration order as they appear in the input set. But the order of the subsets in the outer set is undefined.

Set<Set<Integer>> powerSet = Sets.powerSet(ImmutableSet.of(1, 2, 3));
System.out.println(powerSet); //powerSet({1=0, 3=1, 2=2})

As seen from the above code, printing the power set just prints a user-friendly representation of the input passed. It computes the actual subsets only when we iterate over the result as shown below. Hence, the memory consumption is only O(n) and not 2^n (the number of subsets).

powerSet.forEach(System.out::println);

Prints,

[]
[1]
[2]
[1, 2]
[3]
[1, 3]
[2, 3]
[1, 2, 3]

The power set of the empty set is a one-element set containing the empty set.

Set<Set<Integer>> powerSet = Sets.powerSet(ImmutableSet.of());
powerSet.forEach(System.out::println); //[]

Set#subSet

It takes a NavigableSet and a Range and returns a view of the portion of set whose elements are contained by the passed range.

Here, we have a TreeSet and we pass this with a Range instance constructed using Range.atMost. Since a TreeSet orders based on the natural ordering, the result this subSet call will have all elements less than or equal to the banana. Hence, the result has apple and banana.

NavigableSet<String> fruits = new TreeSet<>(ImmutableSet.of("orange", "banana", "apple", "pear"));
NavigableSet<String> result = Sets.subSet(fruits, Range.atMost("banana"));
System.out.println(result); //[apple, banana]

A few other examples using different Ranges are shown below.

System.out.println(Sets.subSet(fruits, Range.atLeast("banana"))); //[banana, orange, pear]

System.out.println(Sets.subSet(fruits, Range.lessThan("banana"))); //[apple]

System.out.println(Sets.subSet(fruits, Range.greaterThan("banana"))); //[orange, pear]

Sets#combinations

The combinations method takes a set and a size and returns the set of all subsets of the passed size. The elements in the subsets follow the same iteration order as the input set. But the order of the subsets in the outer set is undefined.

Set<Set<Integer>> combinations = Sets.combinations(ImmutableSet.of(1, 2, 3, 4), 2);
System.out.println(combinations); //Sets.combinations([1, 2, 3, 4], 2)

The toString just gives a user-friendly representation of the input. It actually computes the combination (subsets) only when we iterate the resultant set. Hence, the memory usage is only O(n).

combinations
    .forEach(System.out::println);

Prints,

[1, 2]
[1, 3]
[2, 3]
[1, 4]
[2, 4]
[3, 4]

When we pass an invalid value for the size parameter, it throws an IllegalArgumentException.

//throws java.lang.IllegalArgumentException: size (5) must be <= set.size() (4)
Sets.combinations(ImmutableSet.of(1, 2, 3, 4), 5);

Sets#filter

The filter method takes a set and a Google Guava Predicate and returns the set elements which satisfy the predicate. The returned set is a view over the input set.

Here, we have a set of fruits and a Predicate to filter only the fruit names of even length. When these two are passed to the filter method, it returns a set (view) which has only the fruit names of even length.

Set<String> fruits = Set.of("apple", "pear", "banana");
Set<String> evenLengthFruits = Sets.filter(fruits, s -> s.length() % 2 == 0);
System.out.println(evenLengthFruits); //[banana, pear]

Since the returned set is a view, we can add new elements through it. If we add elements which don’t satisfy the predicate, it will throw an IllegalArgumentException.

In the below example, we first add orange through the returned set. Since it satisfies the predicate, it will be added to the underlying set. But if we attempt to an invalid fruit (guava), it throws an IllegalArgumentException.

Set<String> fruits = new HashSet<>(Set.of("apple", "pear", "banana"));
Set<String> evenLengthFruits = Sets.filter(fruits, s -> s.length() % 2 == 0);
evenLengthFruits.add("orange");
System.out.println(evenLengthFruits); //[banana, orange, pear]
System.out.println(fruits); //[banana, orange, apple, pear]

// throws java.lang.IllegalArgumentException
evenLengthFruits.add("guava");

When we remove elements through this view, it will remove only the elements which satisfy the predicate. As an example, removing apple has no effect as its length it odd. But we can remove orange.

evenLengthFruits.remove("apple");
evenLengthFruits.remove("orange");

System.out.println(evenLengthFruits); //[banana, pear]
System.out.println(fruits); //[banana, apple, pear]

There are two other overloaded Sets#filter method which takes a SortedSet and a NavigableSet. An example using NavigableSet is shown below.

NavigableSet<String> sortedFruits = new TreeSet<>(Set.of("apple", "pear", "banana"));
NavigableSet<String> sortedEvenLengthFruits= Sets.filter(sortedFruits, s -> s.length() % 2 == 0);
System.out.println(sortedEvenLengthFruits); //[banana, pear]

Sets#immutableEnumSet

The immutableEnumSet takes a var-args of enum values and returns an immutable set with the passed enum values. Internally, the returned set will be backed by an EnumSet. The iteration order of the returned set follows the enum’s iteration order.

The below example uses the Color enum seen earlier.

ImmutableSet<Color> immutableSet = Sets.immutableEnumSet(Color.GREEN, Color.BLUE, Color.RED);
System.out.println(immutableSet); //[RED, GREEN, BLUE]

There is an overloaded version which takes the enum values as an Iterable as shown below.

List<Color> colors = List.of(Color.GREEN, Color.BLUE, Color.RED);
ImmutableSet<Color> immutableSet = Sets.immutableEnumSet(colors);
System.out.println(immutableSet); //[RED, GREEN, BLUE]

Conclusion

In this post, we looked at the Google Guava Sets utility class.

Note: Google Guava also has a utility method for Lists.

Leave a Reply