43

I have an immutable set (cast as a Set<Integer>) that potentially contains many elements. I need a Collection that contains the elements from that set plus one additional element. I have kludgy code in place to copy the set, then append the element, but I'm looking for The Right Way that keeps things as efficient as possible.

I have Guava available, though I do not require its use.

Max
  • 11,377
  • 5
  • 24
  • 15

7 Answers7

45

Not sure about performance, but you can use Guava's ImmutableSet.Builder:

import com.google.common.collect.ImmutableSet

// ...
Set<Integer> newSet = new ImmutableSet.Builder<Integer>()
                                .addAll(oldSet)
                                .add(3)
                                .build();

Of course you can also write yourself a helper method for that:

public static <T> Set<T> setWith(Set<T> old, T item) {
  return new ImmutableSet.Builder<T>().addAll(old).add(item).build();
}

// ...
Set<Integer> newSet = setWith(oldSet, 3);
Niklas B.
  • 92,950
  • 18
  • 194
  • 224
22

Using Java 8 you can also use streams for that effect

Stream.concat(oldSet.stream(),
              Stream.of(singleElement))
      .collect(Collectors.toSet())

By the way since JDK 10, the Collectors also allow to accumulate to immutable types (the same as the ones created by the static factories like Set.of()) :

Stream.concat(oldSet.stream(),
              Stream.of(singleElement))
      .collect(Collectors.toUnmodifiableSet())
bric3
  • 40,072
  • 9
  • 91
  • 111
8

You might consider Sets.union(). Construction would be faster, but use slower.

public static <T> Set<T> setWith(Set<T> old, T item) {
  return Sets.union(old, Collections.singleton(item);
}

(com.google.common.collect.Sets & java.util.Collections)

Darren Gilroy
  • 2,071
  • 11
  • 6
3

If the Set is immutable, I don't see any way to do it other than copy the Set, and then add your new element. Remember, copying a set is as easy as passing the base set to the constructor function when creating the new set.

hvgotcodes
  • 118,147
  • 33
  • 203
  • 236
3

You have three options.

  • Use a mutable set.
  • Check the element isn't already present, if not create a copy of the set and add an element.
  • Create a wrapper set which includes the previous set and the element.

Sometimes a BitSet is a better choice than Set<Integer> depending on the distribution of your values.

Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
  • Can't use a mutable set - I do not control the API that returns the value. Checking that's it's not present is certainly good advice. Thanks. – Max Apr 27 '12 at 16:30
  • The check is quick compared with the cost of the copy. – Peter Lawrey Apr 27 '12 at 16:31
  • Wouldn't the builder do the check whether it is already present by itself? I mean before allocating a copy. – jmg May 13 '12 at 11:16
1

When you want better performance than a full copy, and you have an ordering over elements, you can use an effectively immutable wrapper around a B+ tree to get good incremental set performance.

Adding an item to a B+ tree requires O(log(n)) time and incremental allocation, not O(n) as you get with ImmutableSet.builder().addAll(...).add(...).build(). This means that building a set from n incremental adds is O(n*log(n)), not O(sqr(n)).

This answer has a pointer to a jdbm library so it might be worth looking at jdbm:jdbm.

Community
  • 1
  • 1
Mike Samuel
  • 118,113
  • 30
  • 216
  • 245
-1

I'm experiencing cognitive dissonance when I read "immutable" and "add to" in the same sentence. You can add a new element to the end of a mutable copy of the immutable values, but you can't modify the immutable set. I don't know of anything elegant.

duffymo
  • 305,152
  • 44
  • 369
  • 561
  • 1
    There is no contradiction here. The number 3 is immutable, but you can still talk about adding 2 to 3 to return 5. – Rich Mar 03 '19 at 13:51
  • You do not understand what is happening. If you add a new element to an immutable set you have not altered it one bit. You’ve created a new set that is a copy of the original immutable one with the new element added – duffymo Mar 03 '19 at 15:43
  • Thanks, but that is exactly my understanding. Your answer here suggests that you did not understand that, but perhaps it is just poorly explained instead. – Rich Mar 03 '19 at 18:42
  • 1
    My point is that the word "add to" does not imply mutation; I had hoped to clarify this by analogy with adding integers. There was no need for you to incorrectly tell me what I do not understand; that was rude. – Rich Mar 03 '19 at 19:00