All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jhotdraw8.icollection.ChampVectorSet Maven / Gradle / Ivy

/*
 * @(#)SimpleImmutableSequencedSet.java
 * Copyright © 2023 The authors and contributors of JHotDraw. MIT License.
 */
package org.jhotdraw8.icollection;

import org.jhotdraw8.icollection.facade.ReadOnlySequencedSetFacade;
import org.jhotdraw8.icollection.immutable.ImmutableSequencedSet;
import org.jhotdraw8.icollection.impl.IdentityObject;
import org.jhotdraw8.icollection.impl.champ.BitmapIndexedNode;
import org.jhotdraw8.icollection.impl.champ.ChangeEvent;
import org.jhotdraw8.icollection.impl.champ.Node;
import org.jhotdraw8.icollection.impl.champ.ReverseTombSkippingVectorSpliterator;
import org.jhotdraw8.icollection.impl.champ.SequencedData;
import org.jhotdraw8.icollection.impl.champ.SequencedElement;
import org.jhotdraw8.icollection.impl.champ.TombSkippingVectorSpliterator;
import org.jhotdraw8.icollection.readonly.ReadOnlyCollection;
import org.jhotdraw8.icollection.readonly.ReadOnlySequencedSet;
import org.jhotdraw8.icollection.readonly.ReadOnlySet;
import org.jhotdraw8.icollection.serialization.SetSerializationProxy;
import org.jspecify.annotations.Nullable;

import java.io.Serial;
import java.io.Serializable;
import java.util.AbstractSet;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Objects;
import java.util.Set;
import java.util.Spliterator;
import java.util.Spliterators;


/**
 * Implements the {@link 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 {@link #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 {@code add} and {@code 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 * {@code 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 *
*
* * @param the element type */ @SuppressWarnings("exports") public class ChampVectorSet implements Serializable, ImmutableSequencedSet { private static final ChampVectorSet EMPTY = new ChampVectorSet<>( BitmapIndexedNode.emptyNode(), VectorList.of(), 0, 0); @Serial private static final long serialVersionUID = 0L; final transient BitmapIndexedNode> root; /** * Offset of sequence numbers to vector indices. * *
vector index = sequence number + offset
*/ final int offset; /** * The size of the set. */ final int size; /** * In this vector we store the elements in the order in which they were inserted. */ final VectorList vector; private record OpaqueRecord(BitmapIndexedNode> root, VectorList vector, int size, int offset) { } /** * Creates a new instance with the provided privateData data object. *

* This constructor is intended to be called from a constructor * of the subclass, that is called from method {@link #newInstance(PrivateData)}. * * @param privateData an privateData data object */ @SuppressWarnings("unchecked") protected ChampVectorSet(PrivateData privateData) { this(((ChampVectorSet.OpaqueRecord) privateData.get()).root, ((ChampVectorSet.OpaqueRecord) privateData.get()).vector, ((ChampVectorSet.OpaqueRecord) privateData.get()).size, ((ChampVectorSet.OpaqueRecord) privateData.get()).offset); } /** * Creates a new instance with the provided privateData object as its internal data structure. *

* Subclasses must override this method, and return a new instance of their subclass! * * @param privateData the internal data structure needed by this class for creating the instance. * @return a new instance of the subclass */ protected ChampVectorSet newInstance(PrivateData privateData) { return new ChampVectorSet<>(privateData); } private ChampVectorSet newInstance(BitmapIndexedNode> root, VectorList vector, int size, int offset) { return new ChampVectorSet<>(new PrivateData(new OpaqueRecord<>(root, vector, size, offset))); } ChampVectorSet( BitmapIndexedNode> root, VectorList vector, int size, int offset) { this.root = root; this.size = size; this.offset = offset; this.vector = Objects.requireNonNull(vector); } /** * Returns an immutable set that contains the provided elements. * * @param c an iterable * @param the element type * @return an immutable set of the provided elements */ @SuppressWarnings("unchecked") public static ChampVectorSet copyOf(Iterable c) { return ChampVectorSet.of().addAll(c); } /** * Returns an empty immutable set. * * @param the element type * @return an empty immutable set */ @SuppressWarnings("unchecked") public static ChampVectorSet of() { return ((ChampVectorSet) ChampVectorSet.EMPTY); } /** * Returns an immutable set that contains the provided elements. * * @param elements elements * @param the element type * @return an immutable set of the provided elements */ @SuppressWarnings({"unchecked", "varargs"}) @SafeVarargs public static ChampVectorSet of(E @Nullable ... elements) { Objects.requireNonNull(elements, "elements is null"); return ChampVectorSet.of().addAll(Arrays.asList(elements)); } @Override public ChampVectorSet add(@Nullable E key) { return addLast(key, false); } @Override @SuppressWarnings({"unchecked"}) public ChampVectorSet addAll(Iterable c) { var m = toMutable(); return m.addAll(c) ? m.toImmutable() : this; } public ChampVectorSet addFirst(@Nullable E element) { return addFirst(element, true); } private ChampVectorSet addFirst(@Nullable E e, boolean moveToFirst) { var details = new ChangeEvent>(); var newElem = new SequencedElement<>(e, -offset - 1); var newRoot = root.put(null, newElem, SequencedElement.keyHash(e), 0, details, moveToFirst ? SequencedElement::putAndMoveToFirst : SequencedElement::put, Objects::equals, SequencedElement::elementKeyHash); if (details.isModified()) { var newVector = vector; int newSize = size; if (details.isReplaced()) { if (moveToFirst) { var result = SequencedData.vecRemove(newVector, details.getOldDataNonNull(), offset); newVector = result.first(); } } else { newSize++; } int newOffset = offset + 1; newVector = newVector.addFirst(newElem); return renumber(newRoot, newVector, newSize, newOffset); } return this; } public ChampVectorSet addLast(@Nullable E element) { return addLast(element, true); } private ChampVectorSet addLast(@Nullable E e, boolean moveToLast) { var details = new ChangeEvent>(); var newElem = new SequencedElement<>(e, vector.size() - offset); var newRoot = root.put(null, newElem, SequencedElement.keyHash(e), 0, details, moveToLast ? SequencedElement::putAndMoveToLast : SequencedElement::put, Objects::equals, SequencedElement::elementKeyHash); if (details.isModified()) { var newVector = vector; int newOffset = offset; int newSize = size; if (details.isReplaced()) { if (moveToLast) { var oldElem = details.getOldData(); var result = SequencedData.vecRemove(newVector, oldElem, newOffset); newVector = result.first(); newOffset = result.second(); } } else { newSize++; } newVector = newVector.addLast(newElem); return renumber(newRoot, newVector, newSize, newOffset); } return this; } /** * {@inheritDoc} */ @Override public ChampVectorSet empty() { return of(); } @Override public boolean contains(@Nullable final Object o) { @SuppressWarnings("unchecked") final E key = (E) o; return root.find(new SequencedElement<>(key), SequencedElement.keyHash(key), 0, Objects::equals) != Node.NO_DATA; } @Override public boolean equals(@Nullable Object other) { if (other == this) { return true; } if (other == null) { return false; } if (other instanceof ChampVectorSet that) { return size == that.size && root.equivalent(that.root); } else { return ReadOnlySet.setEquals(this, other); } } @SuppressWarnings("unchecked") @Override public E getFirst() { return ((SequencedElement) vector.getFirst()).getElement(); } @SuppressWarnings("unchecked") @Override public E getLast() { return ((SequencedElement) vector.getLast()).getElement(); } @Override public int hashCode() { return ReadOnlySet.iteratorToHashCode(iterator()); } @Override public Iterator iterator() { return Spliterators.iterator(spliterator()); } @Override public int maxSize() { return 1 << 30; } @Override public ReadOnlySequencedSet readOnlyReversed() { return new ReadOnlySequencedSetFacade<>( this::reverseIterator, this::iterator, this::size, this::contains, this::getLast, this::getFirst, Spliterator.IMMUTABLE); } @Override public ChampVectorSet remove(@Nullable E key) { int keyHash = SequencedElement.keyHash(key); var details = new ChangeEvent>(); BitmapIndexedNode> newRoot = root.remove(null, new SequencedElement<>(key), keyHash, 0, details, Objects::equals); if (details.isModified()) { var removedElem = details.getOldDataNonNull(); var result = SequencedData.vecRemove(vector, removedElem, offset); return size == 1 ? ChampVectorSet.of() : renumber(newRoot, result.first(), size - 1, result.second()); } return this; } @Override public ChampVectorSet removeAll(Iterable c) { var m = toMutable(); return m.removeAll(c) ? m.toImmutable() : this; } @SuppressWarnings("unchecked") @Override public ChampVectorSet removeFirst() { return remove(getFirst()); } @SuppressWarnings("unchecked") @Override public ChampVectorSet removeLast() { return remove(getLast()); } /** * Renumbers the sequenced elements in the trie if necessary. * * @param root the root of the trie * @param vector the root of the vector * @param size the size of the trie * @param offset the offset that must be added to a sequence number to get the index into the vector * @return a new {@link ChampVectorSet} instance */ private ChampVectorSet renumber( BitmapIndexedNode> root, VectorList vector, int size, int offset) { if (SequencedData.vecMustRenumber(size, offset, this.vector.size())) { var owner = new IdentityObject(); var result = SequencedData.vecRenumber( new IdentityObject(), size, vector.size(), root, vector.trie, SequencedElement::elementKeyHash, Objects::equals, (e, seq) -> new SequencedElement<>(e.getElement(), seq)); return newInstance( result.first(), result.second(), size, 0); } return newInstance(root, vector, size, offset); } @SuppressWarnings("unchecked") @Override public ChampVectorSet retainAll(Iterable c) { var m = toMutable(); return m.retainAll(c) ? m.toImmutable() : this; } Iterator reverseIterator() { return Spliterators.iterator(reverseSpliterator()); } @SuppressWarnings("unchecked") Spliterator reverseSpliterator() { return new ReverseTombSkippingVectorSpliterator<>(vector, e -> ((SequencedElement) e).getElement(), Spliterator.SIZED | Spliterator.DISTINCT | Spliterator.ORDERED | Spliterator.IMMUTABLE, size()); } @Override public int size() { return size; } @SuppressWarnings("unchecked") @Override public Spliterator spliterator() { return new TombSkippingVectorSpliterator<>(vector.trie, e -> ((SequencedElement) e).getElement(), 0, size(), vector.size(), Spliterator.SIZED | Spliterator.DISTINCT | Spliterator.ORDERED | Spliterator.IMMUTABLE); } @Override public MutableChampVectorSet toMutable() { return new MutableChampVectorSet<>(this); } /** * Returns a string representation of this set. *

* The string representation is consistent with the one produced * by {@link AbstractSet#toString()}. * * @return a string representation */ @Override public String toString() { return ReadOnlyCollection.iterableToString(this); } @Serial private Object writeReplace() { return new SerializationProxy<>(toMutable()); } private static class SerializationProxy extends SetSerializationProxy { @Serial private static final long serialVersionUID = 0L; protected SerializationProxy(Set target) { super(target); } @Serial @Override protected Object readResolve() { return ChampVectorSet.copyOf(deserializedElements); } } }