org.jhotdraw8.icollection.MutableChampSet Maven / Gradle / Ivy
/*
* @(#)MutableChampSet.java
* Copyright © 2023 The authors and contributors of JHotDraw. MIT License.
*/
package org.jhotdraw8.icollection;
import org.jhotdraw8.icollection.impl.champ.AbstractMutableChampSet;
import org.jhotdraw8.icollection.impl.champ.BitmapIndexedNode;
import org.jhotdraw8.icollection.impl.champ.BulkChangeEvent;
import org.jhotdraw8.icollection.impl.champ.ChampIterator;
import org.jhotdraw8.icollection.impl.champ.ChampSpliterator;
import org.jhotdraw8.icollection.impl.champ.ChangeEvent;
import org.jhotdraw8.icollection.impl.champ.Node;
import org.jhotdraw8.icollection.impl.iteration.FailFastIterator;
import org.jhotdraw8.icollection.impl.iteration.FailFastSpliterator;
import org.jhotdraw8.icollection.readonly.ReadOnlyCollection;
import org.jhotdraw8.icollection.serialization.SetSerializationProxy;
import org.jspecify.annotations.Nullable;
import java.io.Serial;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Objects;
import java.util.Set;
import java.util.Spliterator;
import java.util.function.Function;
/**
* Implements the {@link Set} interface using a Compressed Hash-Array Mapped
* Prefix-tree (CHAMP).
*
* Features:
*
* - supports up to 231 - 1 elements
* - allows null elements
* - is mutable
* - is not thread-safe
* - does not guarantee a specific iteration order
*
*
* Performance characteristics:
*
* - add: O(log₃₂ N)
* - remove: O(log₃₂ N)
* - contains: O(log₃₂ N)
* - toImmutable: O(1) + O(log₃₂ N) distributed across subsequent updates in
* this set
* - clone: O(1) + O(log₃₂ N) distributed across subsequent updates in this
* set and in the clone
* - iterator.next: O(1)
*
*
* Implementation details:
*
* See description at {@link ChampSet}.
*
* References:
*
* Portions of the code in this class has been derived from 'The Capsule Hash Trie Collections Library'.
*
* - Michael J. Steindorfer (2017).
* Efficient Immutable Collections.
* - michael.steindorfer.name
*
- The Capsule Hash Trie Collections Library.
*
Copyright (c) Michael Steindorfer. BSD-2-Clause License
* - github.com
*
*
* @param the element type
*/
public class MutableChampSet extends AbstractMutableChampSet {
@Serial
private static final long serialVersionUID = 0L;
/**
* Constructs a new empty set.
*/
public MutableChampSet() {
root = BitmapIndexedNode.emptyNode();
}
/**
* Constructs a set containing the elements in the specified iterable.
*
* @param c an iterable
*/
@SuppressWarnings({"unchecked", "this-escape"})
public MutableChampSet(Iterable extends E> c) {
if (c instanceof MutableChampSet>) {
c = ((MutableChampSet extends E>) c).toImmutable();
}
if (c instanceof ChampSet>) {
ChampSet that = (ChampSet) c;
this.root = that.root;
this.size = that.size;
} else {
this.root = BitmapIndexedNode.emptyNode();
addAll(c);
}
}
@Override
public boolean add(@Nullable E e) {
ChangeEvent details = new ChangeEvent<>();
root = root.put(makeOwner(),
e, ChampSet.keyHash(e), 0, details,
(oldKey, newKey) -> oldKey,
Objects::equals, ChampSet::keyHash);
if (details.isModified()) {
size++;
modCount++;
}
return details.isModified();
}
/**
* Adds all specified elements that are not already in this set.
*
* @param c an iterable of elements
* @return {@code true} if this set changed
*/
@SuppressWarnings("unchecked")
public boolean addAll(Iterable extends E> c) {
if (c instanceof MutableChampSet> m) {
c = (Iterable extends E>) m.toImmutable();
}
if (isEmpty() && (c instanceof ChampSet> cc)) {
root = (BitmapIndexedNode) cc.root;
size = cc.size;
return true;
}
if (c instanceof ChampSet> that) {
var bulkChange = new BulkChangeEvent();
var newRootNode = root.putAll(makeOwner(), (Node) that.root, 0, bulkChange, ChampSet::updateElement, Objects::equals, ChampSet::keyHash, new ChangeEvent<>());
if (bulkChange.inBoth == that.size()) {
return false;
}
root = newRootNode;
size += that.size - bulkChange.inBoth;
modCount++;
return true;
}
return super.addAll(c);
}
@Override
public boolean removeAll(Collection> c) {
return removeAll((Iterable>) c);
}
@SuppressWarnings("unchecked")
@Override
public boolean removeAll(Iterable> c) {
if (isEmpty()
|| (c instanceof Collection> cc) && cc.isEmpty()
|| (c instanceof ReadOnlyCollection> rc) && rc.isEmpty()) {
return false;
}
if (c == this) {
clear();
return true;
}
if (c instanceof MutableChampSet> m) {
c = m.toImmutable();
}
if (c instanceof ChampSet> that) {
BulkChangeEvent bulkChange = new BulkChangeEvent();
BitmapIndexedNode newRootNode = root.removeAll(makeOwner(), (BitmapIndexedNode) that.root, 0, bulkChange, ChampSet::updateElement, Objects::equals, ChampSet::keyHash, new ChangeEvent<>());
if (bulkChange.removed == 0) {
return false;
}
root = newRootNode;
size -= bulkChange.removed;
modCount++;
return true;
}
return super.removeAll(c);
}
@SuppressWarnings("unchecked")
@Override
public boolean retainAll(Collection> c) {
if (isEmpty()) {
return false;
}
if (c.isEmpty()) {
clear();
return true;
}
if (c instanceof MutableChampSet> m) {
ChampSet> that = m.toImmutable();
BulkChangeEvent bulkChange = new BulkChangeEvent();
BitmapIndexedNode newRootNode = root.retainAll(makeOwner(), (BitmapIndexedNode) that.root, 0, bulkChange, ChampSet::updateElement, Objects::equals, ChampSet::keyHash, new ChangeEvent<>());
if (bulkChange.removed == 0) {
return false;
}
root = newRootNode;
size -= bulkChange.removed;
modCount++;
return true;
}
return super.retainAll(c);
}
@SuppressWarnings("unchecked")
public boolean retainAll(Iterable> c) {
if (c == this || isEmpty()) {
return false;
}
if ((c instanceof Collection> cc && cc.isEmpty())
|| (c instanceof ReadOnlyCollection> rc) && rc.isEmpty()) {
clear();
return true;
}
BulkChangeEvent bulkChange = new BulkChangeEvent();
BitmapIndexedNode newRootNode;
switch (c) {
case ChampSet> that ->
newRootNode = root.retainAll(makeOwner(), (BitmapIndexedNode) that.root, 0, bulkChange, ChampSet::updateElement, Objects::equals, ChampSet::keyHash, new ChangeEvent<>());
case Collection> that -> newRootNode = root.filterAll(makeOwner(), that::contains, 0, bulkChange);
case ReadOnlyCollection> that -> newRootNode = root.filterAll(makeOwner(), that::contains, 0, bulkChange);
default -> {
HashSet