TreeSet Flashcards
TreeSet
Intro to TreeSet
Simply put, the TreeSet is a sorted collection that extends the AbstractSet class and implements the NavigableSet interface.
Here’s a quick summary of the most important aspects of this implementation:
It stores unique elements
It doesn’t preserve the insertion order of the elements
It sorts the elements in ascending order
It’s not thread-safe
Example Of TreeSet
In this implementation, objects are sorted and stored in ascending order according to their natural order. The TreeSet uses a self-balancing binary search tree, more specifically a Red-Black tree.
Simply put, being a self-balancing binary search tree, each node of the binary tree comprises of an extra bit, which is used to identify the color of the node which is either red or black. During subsequent insertions and deletions, these “color” bits helps in ensuring that the tree remains more or less balanced.
So, let’s create an instance of a TreeSet:
1
Set treeSet = new TreeSet<>();
TreeSet with a Constructor Comparator Param
Optionally, we can construct a TreeSet with a constructor that lets us define the order in which the elements get sorted by using a Comparable or Comparator:
Set treeSet = new TreeSet<>(Comparator.comparing(String::length));
Is TreeSet Synchronized?
No
Can TreeSet be Synchronized?
Although TreeSet isn’t thread-safe, it can be synchronized externally using the Collections.synchronizedSet() wrapper:
Set syncTreeSet = Collections.synchronizedSet(treeSet);
TreeSet add()
The add() method, as expected, can be used for adding elements to a TreeSet. If an element was added, the method returns true, otherwise – false.
The contract of the method states that an element will be added only when the same isn’t already present in the Set.
Let’s add an element to a TreeSet:
@Test
public void whenAddingElement_shouldAddElement() {
Set treeSet = new TreeSet<>();
assertTrue(treeSet.add("String Added")); }
The add method is extremely important as the implementation details of the method illustrate how the TreeSet works internally, how it leverages the TreeMap’s put method to store the elements:
public boolean add(E e) {
return m.put(e, PRESENT) == null;
}
The variable m refers to an internal backing TreeMap (note that TreeMap implements NavigateableMap):
private transient NavigableMap m;
Therefore, the TreeSet internally depends on a backing NavigableMap which gets initialized with an instance of TreeMap when an instance of the TreeSet is created:
public TreeSet() { this(new TreeMap()); }
TreeSet contains()
The contains() method is used to check if a given element is present in a given TreeSet. If the element is found, it returns true, otherwise false.
Let’s see the contains() in action:
@Test
public void whenCheckingForElement_shouldSearchForElement() {
Set treeSetContains = new TreeSet<>();
treeSetContains.add(“String Added”);
assertTrue(treeSetContains.contains("String Added")); }
TreeSet remove()
The remove() method is used to remove the specified element from the set if it’s present.
If a set contained the specified element, this method returns true.
Let’s see it in action:
@Test
public void whenRemovingElement_shouldRemoveElement() {
Set removeFromTreeSet = new TreeSet<>();
removeFromTreeSet.add(“String Added”);
assertTrue(removeFromTreeSet.remove("String Added")); }
TreeSet clear()
If we want to remove all the items from a set, we can use the clear() method:
@Test public void whenClearingTreeSet_shouldClearTreeSet() { Set clearTreeSet = new TreeSet<>(); clearTreeSet.add("String Added"); clearTreeSet.clear();
assertTrue(clearTreeSet.isEmpty()); }
TreeSet
size() ,
isEmpty()
The size() method is used to identify the number of elements present in the TreeSet. It’s one of the fundamental methods in the API:
@Test
public void whenCheckingTheSizeOfTreeSet_shouldReturnThesize() {
Set treeSetSize = new TreeSet<>();
treeSetSize.add(“String Added”);
assertEquals(1, treeSetSize.size());
Set emptyTreeSet = new TreeSet<>();
assertTrue(emptyTreeSet.isEmpty()); }
TreeSet iterator()
The iterator() method returns an iterator iterating in the ascending order over the elements in the Set. Those iterators are fail-fast.
We can observe the ascending iteration order here:
@Test public void whenIteratingTreeSet_shouldIterateTreeSetInAscendingOrder() { Set treeSet = new TreeSet<>(); treeSet.add("First"); treeSet.add("Second"); treeSet.add("Third"); Iterator itr = treeSet.iterator(); while (itr.hasNext()) { System.out.println(itr.next()); } } Additionally, TreeSet enables us to iterate through the Set in descending order.
Let’s see that in action: @Test public void whenIteratingTreeSet_shouldIterateTreeSetInDescendingOrder() { TreeSet treeSet = new TreeSet<>(); treeSet.add("First"); treeSet.add("Second"); treeSet.add("Third"); Iterator itr = treeSet.descendingIterator(); while (itr.hasNext()) { System.out.println(itr.next()); } }
When TreeSet Iterator throws,
ConcurrentModificationException
The Iterator throws a ConcurrentModificationException if the set is modified at any time after the iterator is created in any way except through the iterator’s remove() method.
Let’s create a test for this:
@Test(expected = ConcurrentModificationException.class) public void whenModifyingTreeSetWhileIterating_shouldThrowException() { Set treeSet = new TreeSet<>(); treeSet.add("First"); treeSet.add("Second"); treeSet.add("Third"); Iterator itr = treeSet.iterator(); while (itr.hasNext()) { itr.next(); treeSet.remove("Second"); } } Alternatively, if we had used the iterator’s remove method, then we wouldn’t have encountered the exception:
@Test
public void whenRemovingElementUsingIterator_shouldRemoveElement() {
Set treeSet = new TreeSet<>(); treeSet.add("First"); treeSet.add("Second"); treeSet.add("Third"); Iterator itr = treeSet.iterator(); while (itr.hasNext()) { String element = itr.next(); if (element.equals("Second")) itr.remove(); }
assertEquals(2, treeSet.size()); } There’s no guarantee on the fail-fast behavior of an iterator as it’s impossible to make any hard guarantees in the presence of unsynchronized concurrent modification.
TreeSet first()
This method returns the first element from a TreeSet if it’s not empty. Otherwise, it throws a NoSuchElementException.
Let’s see an example:
@Test
public void whenCheckingFirstElement_shouldReturnFirstElement() {
TreeSet treeSet = new TreeSet<>();
treeSet.add(“First”);
assertEquals("First", treeSet.first()); }
TreeSet last()
Analogously to the above example, this method will return the last element if the set is not empty:
@Test public void whenCheckingLastElement_shouldReturnLastElement() { TreeSet treeSet = new TreeSet<>(); treeSet.add("First"); treeSet.add("Last");
assertEquals("Last", treeSet.last()); }
TreeSet subSet()
This method will return the elements ranging from fromElement to toElement. Note that fromElement is inclusive and toElement is exclusive:
@Test public void whenUsingSubSet_shouldReturnSubSetElements() { SortedSet treeSet = new TreeSet<>(); treeSet.add(1); treeSet.add(2); treeSet.add(3); treeSet.add(4); treeSet.add(5); treeSet.add(6);
Set expectedSet = new TreeSet<>(); expectedSet.add(2); expectedSet.add(3); expectedSet.add(4); expectedSet.add(5);
Set subSet = treeSet.subSet(2, 6); assertEquals(expectedSet, subSet); }