Class ChampVectorSet<E>

java.lang.Object
org.jhotdraw8.icollection.ChampVectorSet<E>
Type Parameters:
E - the element type
All Implemented Interfaces:
Serializable, Iterable<E>, ImmutableCollection<E>, ImmutableSequencedCollection<E>, ImmutableSequencedSet<E>, ImmutableSet<E>, ReadOnlyCollection<E>, ReadOnlySequencedCollection<E>, ReadOnlySequencedSet<E>, ReadOnlySet<E>

public class ChampVectorSet<E> extends Object implements Serializable, ImmutableSequencedSet<E>
Implements the ImmutableSequencedSet interface using a Compressed Hash-Array Mapped Prefix-tree (CHAMP) and a bit-mapped trie (Vector).

Features:

  • supports up to 230 elements
  • allows null elements
  • is immutable
  • is thread-safe
  • iterates in the order, in which elements were inserted

Performance characteristics:

  • add: O(log₃₂ N) in an amortized sense, because we sometimes have to renumber the elements.
  • remove: O(log₃₂ N) in an amortized sense, because we sometimes have to renumber the elements.
  • contains: O(log₃₂ N)
  • toMutable: O(1) + O(log₃₂ N) distributed across subsequent updates in the mutable copy
  • clone: O(1)
  • iterator creation: O(log₃₂ N)
  • iterator.next: O(1)
  • getFirst(), getLast(): O(log₃₂ N)

Implementation details:

This set performs read and write operations of single elements in O(log N) time, and in O(log N) space, where N is the number of elements in the set.

The CHAMP trie contains nodes that may be shared with other sets.

If a write operation is performed on a node, then this set creates a copy of the node and of all parent nodes up to the root (copy-path-on-write). Since the CHAMP trie has a fixed maximal height, the cost is O(1).

This set can create a mutable copy of itself in O(1) time and O(1) space using method toMutable(). The mutable copy shares its nodes with this set, until it has gradually replaced the nodes with exclusively owned nodes.

Insertion Order:

This set uses a counter to keep track of the insertion order. It stores the current value of the counter in the sequence number field of each data entry. If the counter wraps around, it must renumber all sequence numbers.

The renumbering is why the add and remove methods are O(1) only in an amortized sense.

To support iteration, we use a Vector. The Vector has the same contents as the CHAMP trie. However, its elements are stored in insertion order.

If an element is removed from the CHAMP trie that is not the first or the last element of the Vector, we replace its corresponding element in the Vector by a tombstone. If the element is at the start or end of the Vector, we remove the element and all its neighboring tombstones from the Vector.

A tombstone can store the number of neighboring tombstones in ascending and in descending direction. We use these numbers to skip tombstones when we iterate over the vector. Since we only allow iteration in ascending or descending order from one of the ends of the vector, we do not need to keep the number of neighbors in all tombstones up to date. It is sufficient, if we update the neighbor with the lowest index and the one with the highest index.

If the number of tombstones exceeds half of the size of the collection, we renumber all sequence numbers, and we create a new Vector.

The immutable version of this set extends from the non-public class ChampBitmapIndexNode. This design safes 16 bytes for every instance, and reduces the number of redirections for finding an element in the collection by 1.

References:

For a similar design, see 'SimpleImmutableSequencedMap.scala'. Note, that this code is not a derivative of that code.

The Scala library. SimpleImmutableSequencedMap.scala. Copyright EPFL and Lightbend, Inc. Apache License 2.0.
github.com
See Also: