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

com.github.tommyettinger.ds.HolderOrderedSet Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2022-2025 See AUTHORS file.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.github.tommyettinger.ds;

import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.NoSuchElementException;
import com.github.tommyettinger.function.ObjToObjFunction;

import static com.github.tommyettinger.ds.Utilities.tableSize;

/**
 * A {@link HolderSet} that also stores items in an {@link ObjectList} using the insertion order. This acts like
 * HolderSet instead of like {@link ObjectSet}, with the constructors typically taking an extractor function,
 * {@link #contains(Object)} and {@link #remove(Object)} accepting a K key instead of a T item, and
 * {@link #get(Object)} used to get a T item from a K key. Neither null items nor null keys are allowed.
 * No allocation is done except when growing the table size.
 * 

* {@link #iterator() Iteration} is ordered over items and faster than an unordered set. Items can also be accessed * and the order changed using {@link #order()}. There is some additional overhead for put and remove. *

* This class performs fast contains (typically O(1), worst case O(n) but that is rare in practice). Remove is somewhat slower due * to {@link #order()}. Add may be slightly slower, depending on hash collisions. Hashcodes are rehashed to reduce * collisions and the need to resize. Load factors greater than 0.91 greatly increase the chances to resize to the next higher POT * size. *

* Unordered sets and maps are not designed to provide especially fast iteration. Iteration is faster with {@link Ordered} types like * HolderOrderedSet and ObjectObjectOrderedMap. *

* You can customize most behavior of this set by extending it. {@link #place(Object)} can be overridden to change how hashCodes * are calculated on K keys (which can be useful for types like {@link StringBuilder} that don't implement hashCode()), and * {@link #locateKey(Object)} can be overridden to change how equality is calculated for K keys. *

* This implementation uses linear probing with the backward shift algorithm for removal. * It tries different hashes from a simple family, with the hash changing on resize. * Linear probing continues to work even when all hashCodes collide, just more slowly. * * @author Nathan Sweet * @author Tommy Ettinger */ public class HolderOrderedSet extends HolderSet implements Ordered { protected final ObjectList items; /** * Creates a new set with an initial capacity of 51 and a load factor of {@link Utilities#getDefaultLoadFactor()}. This does not set the * extractor, so the HolderSet will not be usable until {@link #setExtractor(ObjToObjFunction)} is called with * a valid ObjToObjFunction that gets K keys from T items. */ public HolderOrderedSet () { super(); items = new ObjectList<>(); } /** * Creates a new set with an initial capacity of 51 and a load factor of {@link Utilities#getDefaultLoadFactor()}. * * @param extractor a function that will be used to extract K keys from the T items put into this */ public HolderOrderedSet (ObjToObjFunction extractor) { super(extractor); items = new ObjectList<>(); } /** * Creates a new set with a load factor of {@link Utilities#getDefaultLoadFactor()}. * * @param extractor a function that will be used to extract K keys from the T items put into this * @param initialCapacity If not a power of two, it is increased to the next nearest power of two. */ public HolderOrderedSet (ObjToObjFunction extractor, int initialCapacity) { super(extractor, initialCapacity); items = new ObjectList<>(initialCapacity); } /** * Creates a new set with the specified initial capacity and load factor. This set will hold initialCapacity items before * growing the backing table. * * @param extractor a function that will be used to extract K keys from the T items put into this * @param initialCapacity If not a power of two, it is increased to the next nearest power of two. * @param loadFactor what fraction of the capacity can be filled before this has to resize; 0 < loadFactor <= 1 */ public HolderOrderedSet (ObjToObjFunction extractor, int initialCapacity, float loadFactor) { super(extractor, initialCapacity, loadFactor); items = new ObjectList<>(initialCapacity); } /** * Creates a new instance containing the items in the specified iterator. * * @param coll an iterator that will have its remaining contents added to this */ public HolderOrderedSet (@NonNull ObjToObjFunction extractor, Iterator coll) { this(extractor); addAll(coll); } /** * Creates a new set identical to the specified set. * This doesn't copy the extractor; instead it references the same ObjToObjFunction from the argument. * This can have issues if the extractor causes side effects or is stateful. */ public HolderOrderedSet (HolderOrderedSet set) { super(set); items = new ObjectList<>(set.items); } /** * Creates a new set that contains all distinct elements in {@code coll}, using {@code extractor} to get the keys that determine distinctness. * * @param extractor a function that will be used to extract K keys from the T items in coll * @param coll a Collection of T items; depending on extractor, some different T items may not be added because their K key is equal */ public HolderOrderedSet (ObjToObjFunction extractor, Collection coll) { this(extractor, coll.size()); addAll(coll); } /** * Creates a new set that contains all distinct elements in {@code items}, using {@code extractor} to get the keys that determine distinctness. * * @param extractor a function that will be used to extract K keys from the T items in coll * @param items an array of T items; depending on extractor, some different T items may not be added because their K key is equal */ public HolderOrderedSet (ObjToObjFunction extractor, T[] items) { this(extractor, items.length); addAll(items); } /** * Creates a new set by copying {@code count} items from the given Ordered, starting at {@code offset} in that Ordered, * into this, using {@code extractor} to get the keys that determine distinctness. * * @param extractor a function that will be used to extract K keys from the T items in coll * @param other another Ordered of the same type * @param offset the first index in other's ordering to draw an item from * @param count how many items to copy from other */ public HolderOrderedSet (ObjToObjFunction extractor, Ordered other, int offset, int count) { this(extractor, count); addAll(0, other, offset, count); } @Override public boolean add (T key) { return super.add(key) && items.add(key); } /** * Sets the key at the specified index. Returns true if the key was not already in the set. If this set already contains the * key, the existing key's index is changed if needed and false is returned. Note, the order of the parameters matches the * order in {@link ObjectList} and the rest of the JDK, not OrderedSet in libGDX. * * @param index where in the iteration order to add the given key, or to move it if already present * @param key what T item to try to add, if not already present * @return true if the key was added for the first time, or false if the key was already present (even if moved) */ public boolean add (int index, T key) { if (!super.add(key)) { int oldIndex = items.indexOf(key); if (oldIndex != index) {items.add(index, items.remove(oldIndex));} return false; } items.add(index, key); return true; } public boolean addAll (HolderOrderedSet set) { ensureCapacity(set.size); ObjectList si = set.items; int oldSize = size; for (int i = 0, n = si.size(); i < n; i++) {add(si.get(i));} return size != oldSize; } /** * Adds up to {@code count} items, starting from {@code offset}, in the Ordered {@code other} to this set, * inserting at the end of the iteration order. * * @param other a non-null {@link Ordered} of {@code T} * @param offset the first index in {@code other} to use * @param count how many indices in {@code other} to use * @return true if this is modified by this call, as {@link #addAll(Collection)} does */ public boolean addAll (Ordered other, int offset, int count) { return addAll(size, other, offset, count); } /** * Adds up to {@code count} items, starting from {@code offset}, in the Ordered {@code other} to this set, * inserting starting at {@code insertionIndex} in the iteration order. * * @param insertionIndex where to insert into the iteration order * @param other a non-null {@link Ordered} of {@code T} * @param offset the first index in {@code other} to use * @param count how many indices in {@code other} to use * @return true if this is modified by this call, as {@link #addAll(Collection)} does */ public boolean addAll (int insertionIndex, Ordered other, int offset, int count) { boolean changed = false; int end = Math.min(offset + count, other.size()); ensureCapacity(end - offset); for (int i = offset; i < end; i++) { add(insertionIndex++, other.order().get(i)); changed = true; } return changed; } /** * Takes a K key to remove from this HolderOrderedSet, not a T item. * * @param key should be a K key, not a T item * @return true if this was modified */ @Override public boolean remove (Object key) { return items.remove(super.get(key)) && super.remove(key); } /** * Removes and returns the item at the given index in this set's order. * * @param index the index of the item to remove * @return the removed item */ public T removeAt (int index) { T item = items.removeAt(index); assert extractor != null; super.remove(extractor.apply(item)); return item; } /** * Gets the first item in the order. * * @return the first item in this set's order * @throws IllegalStateException if this is empty. */ @Override public T first () { if (size == 0) throw new IllegalStateException("HolderOrderedSet is empty."); return items.get(0); } /** * Increases the size of the backing array to accommodate the specified number of additional items / loadFactor. Useful before * adding many items to avoid multiple backing array resizes. * * @param additionalCapacity how many additional items this should be able to hold without resizing (probably) */ @Override public void ensureCapacity (int additionalCapacity) { int tableSize = tableSize(size + additionalCapacity, loadFactor); if (keyTable.length < tableSize) {resize(tableSize);} items.ensureCapacity(additionalCapacity); } /** * Changes the item {@code before} to {@code after} without changing its position in the order. Returns true if {@code after} * has been added to the ObjectOrderedSet and {@code before} has been removed; returns false if {@code after} is already present or * {@code before} is not present. If you are iterating over an ObjectOrderedSet and have an index, you should prefer * {@link #alterAt(int, Object)}, which doesn't need to search for an index like this does and so can be faster. * * @param before an item that must be present for this to succeed * @param after an item that must not be in this set for this to succeed * @return true if {@code before} was removed and {@code after} was added, false otherwise */ public boolean alter (T before, T after) { if (contains(extractor.apply(after))) {return false;} if (!super.remove(extractor.apply(before))) {return false;} super.add(after); items.set(items.indexOf(before), after); return true; } /** * Changes the item at the given {@code index} in the order to {@code after}, without changing the ordering of other items. If * {@code after} is already present, this returns false; it will also return false if {@code index} is invalid for the size of * this set. Otherwise, it returns true. Unlike {@link #alter(Object, Object)}, this operates in constant time. * * @param index the index in the order of the item to change; must be non-negative and less than {@link #size} * @param after the item that will replace the contents at {@code index}; this item must not be present for this to succeed * @return true if {@code after} successfully replaced the contents at {@code index}, false otherwise */ public boolean alterAt (int index, T after) { if (index < 0 || index >= size || contains(extractor.apply(after))) {return false;} super.remove(items.get(index)); super.add(after); items.set(index, after); return true; } /** * Gets the T item at the given {@code index} in the insertion order. The index should be between 0 (inclusive) and * {@link #size()} (exclusive). * * @param index an index in the insertion order, between 0 (inclusive) and {@link #size()} (exclusive) * @return the item at the given index */ public T getAt (int index) { return items.get(index); } @Override public void clear (int maximumCapacity) { items.clear(); super.clear(maximumCapacity); } @Override public void clear () { items.clear(); super.clear(); } /** * Gets the ObjectList of T items in the order this class will iterate through them. * Returns a direct reference to the same ObjectList this uses, so changes to the returned list will * also change the iteration order here. * * @return the ObjectList of T items, in iteration order (usually insertion-order), that this uses */ @Override public ObjectList order () { return items; } /** * Sorts this ObjectOrderedSet in-place by the T items' natural ordering; {@code T} must implement {@link Comparable}. */ public void sort () { items.sort(null); } /** * Sorts this ObjectOrderedSet in-place by the given Comparator used on the T items. If {@code comp} is null, then this * will sort by the natural ordering of the items, which requires {@code T} to {@link Comparable}. * * @param comp a Comparator that can compare two {@code T} items, or null to use the items' natural ordering */ public void sort (@Nullable Comparator comp) { items.sort(comp); } /** * Removes the items between the specified start index, inclusive, and end index, exclusive. * Note that this takes different arguments than some other range-related methods; this needs * a start index and an end index, rather than a count of items. This matches the behavior in * the JDK collections. * * @param start the first index to remove, inclusive * @param end the last index (after what should be removed), exclusive */ @Override public void removeRange (int start, int end) { start = Math.max(0, start); end = Math.min(items.size(), end); for (int i = start; i < end; i++) { super.remove(items.get(i)); } items.removeRange(start, end); } /** * Reduces the size of the set to the specified size. If the set is already smaller than the specified * size, no action is taken. * * @param newSize the target size to try to reach by removing items, if smaller than the current size */ @Override public void truncate (int newSize) { if (size > newSize) {removeRange(newSize, size);} } /** * Iterates through items in the same order as {@link #order()}. * Reuses one of two iterators, and does not permit nested iteration; * use {@link HolderOrderedSetIterator#HolderOrderedSetIterator(HolderOrderedSet)} to nest iterators. * * @return an {@link Iterator} over the T items in this, in order */ @Override public @NonNull HolderSetIterator iterator () { if (iterator1 == null || iterator2 == null) { iterator1 = new HolderOrderedSetIterator<>(this); iterator2 = new HolderOrderedSetIterator<>(this); } if (!iterator1.valid) { iterator1.reset(); iterator1.valid = true; iterator2.valid = false; return iterator1; } iterator2.reset(); iterator2.valid = true; iterator1.valid = false; return iterator2; } @Override public String toString (String separator) { if (size == 0) {return "{}";} ObjectList items = this.items; StringBuilder buffer = new StringBuilder(32); buffer.append('{'); buffer.append(items.get(0)); for (int i = 1; i < size; i++) { buffer.append(separator); buffer.append(items.get(i)); } buffer.append('}'); return buffer.toString(); } @Override public String toString () { return toString(", "); } public static class HolderOrderedSetIterator extends HolderSetIterator { protected final ObjectList items; public HolderOrderedSetIterator (HolderOrderedSet set) { super(set); items = set.items; } @Override public void reset () { nextIndex = 0; hasNext = set.size > 0; } @Override public T next () { if (!hasNext) {throw new NoSuchElementException();} if (!valid) {throw new RuntimeException("#iterator() cannot be used nested.");} T key = items.get(nextIndex); nextIndex++; hasNext = nextIndex < set.size; return key; } @Override public void remove () { if (nextIndex < 0) {throw new IllegalStateException("next must be called before remove.");} nextIndex--; assert set.extractor != null; set.remove(set.extractor.apply(items.get(nextIndex))); } } /** * Constructs an empty set given only an extractor function. * This is usually less useful than just using the constructor, but can be handy * in some code-generation scenarios when you don't know how many arguments you will have. * * @param extractor a ObjToObjFunction that takes a T and gets a unique K from it; often a method reference * @param the type of items; must be given explicitly * @param the type of keys that extractor pulls from T items * @return a new set containing nothing */ public static HolderOrderedSet with(ObjToObjFunction extractor) { return new HolderOrderedSet<>(extractor, 0); } /** * Creates a new HolderOrderedSet that holds only the given item, but can be resized. * @param extractor a ObjToObjFunction that takes a T and gets a unique K from it; often a method reference * @param item one T item * @return a new HolderOrderedSet that holds the given item * @param the type of item, typically inferred * @param the type of keys that extractor pulls from T items */ public static HolderOrderedSet with(ObjToObjFunction extractor, T item) { HolderOrderedSet set = new HolderOrderedSet<>(extractor, 1); set.add(item); return set; } /** * Creates a new HolderOrderedSet that holds only the given items, but can be resized. * @param extractor a ObjToObjFunction that takes a T and gets a unique K from it; often a method reference * @param item0 a T item * @param item1 a T item * @return a new HolderOrderedSet that holds the given items * @param the type of item, typically inferred * @param the type of keys that extractor pulls from T items */ public static HolderOrderedSet with(ObjToObjFunction extractor, T item0, T item1) { HolderOrderedSet set = new HolderOrderedSet<>(extractor, 2); set.add(item0, item1); return set; } /** * Creates a new HolderOrderedSet that holds only the given items, but can be resized. * @param extractor a ObjToObjFunction that takes a T and gets a unique K from it; often a method reference * @param item0 a T item * @param item1 a T item * @param item2 a T item * @return a new HolderOrderedSet that holds the given items * @param the type of item, typically inferred * @param the type of keys that extractor pulls from T items */ public static HolderOrderedSet with(ObjToObjFunction extractor, T item0, T item1, T item2) { HolderOrderedSet set = new HolderOrderedSet<>(extractor, 3); set.add(item0, item1, item2); return set; } /** * Creates a new HolderOrderedSet that holds only the given items, but can be resized. * @param extractor a ObjToObjFunction that takes a T and gets a unique K from it; often a method reference * @param item0 a T item * @param item1 a T item * @param item2 a T item * @param item3 a T item * @return a new HolderOrderedSet that holds the given items * @param the type of item, typically inferred * @param the type of keys that extractor pulls from T items */ public static HolderOrderedSet with(ObjToObjFunction extractor, T item0, T item1, T item2, T item3) { HolderOrderedSet set = new HolderOrderedSet<>(extractor, 4); set.add(item0, item1, item2, item3); return set; } /** * Creates a new HolderOrderedSet that holds only the given items, but can be resized. * @param extractor a ObjToObjFunction that takes a T and gets a unique K from it; often a method reference * @param item0 a T item * @param item1 a T item * @param item2 a T item * @param item3 a T item * @param item4 a T item * @return a new HolderOrderedSet that holds the given items * @param the type of item, typically inferred * @param the type of keys that extractor pulls from T items */ public static HolderOrderedSet with(ObjToObjFunction extractor, T item0, T item1, T item2, T item3, T item4) { HolderOrderedSet set = new HolderOrderedSet<>(extractor, 5); set.add(item0, item1, item2, item3); set.add(item4); return set; } /** * Creates a new HolderOrderedSet that holds only the given items, but can be resized. * @param extractor a ObjToObjFunction that takes a T and gets a unique K from it; often a method reference * @param item0 a T item * @param item1 a T item * @param item2 a T item * @param item3 a T item * @param item4 a T item * @param item5 a T item * @return a new HolderOrderedSet that holds the given items * @param the type of item, typically inferred * @param the type of keys that extractor pulls from T items */ public static HolderOrderedSet with(ObjToObjFunction extractor, T item0, T item1, T item2, T item3, T item4, T item5) { HolderOrderedSet set = new HolderOrderedSet<>(extractor, 6); set.add(item0, item1, item2, item3); set.add(item4, item5); return set; } /** * Creates a new HolderOrderedSet that holds only the given items, but can be resized. * @param extractor a ObjToObjFunction that takes a T and gets a unique K from it; often a method reference * @param item0 a T item * @param item1 a T item * @param item2 a T item * @param item3 a T item * @param item4 a T item * @param item5 a T item * @param item6 a T item * @return a new HolderOrderedSet that holds the given items * @param the type of item, typically inferred * @param the type of keys that extractor pulls from T items */ public static HolderOrderedSet with(ObjToObjFunction extractor, T item0, T item1, T item2, T item3, T item4, T item5, T item6) { HolderOrderedSet set = new HolderOrderedSet<>(extractor, 7); set.add(item0, item1, item2, item3); set.add(item4, item5, item6); return set; } /** * Creates a new HolderOrderedSet that holds only the given items, but can be resized. * @param extractor a ObjToObjFunction that takes a T and gets a unique K from it; often a method reference * @param item0 a T item * @param item1 a T item * @param item2 a T item * @param item3 a T item * @param item4 a T item * @param item5 a T item * @param item6 a T item * @return a new HolderOrderedSet that holds the given items * @param the type of item, typically inferred * @param the type of keys that extractor pulls from T items */ public static HolderOrderedSet with(ObjToObjFunction extractor, T item0, T item1, T item2, T item3, T item4, T item5, T item6, T item7) { HolderOrderedSet set = new HolderOrderedSet<>(extractor, 8); set.add(item0, item1, item2, item3); set.add(item4, item5, item6, item7); return set; } /** * Creates a new HolderOrderedSet that holds only the given items, but can be resized. * This overload will only be used when an array is supplied and the type of the * items requested is the component type of the array, or if varargs are used and * there are 9 or more arguments. * @param extractor a ObjToObjFunction that takes a T and gets a unique K from it; often a method reference * @param varargs a T varargs or T array; remember that varargs allocate * @return a new HolderOrderedSet that holds the given items * @param the type of item, typically inferred * @param the type of keys that extractor pulls from T items */ @SafeVarargs public static HolderOrderedSet with(ObjToObjFunction extractor, T... varargs) { return new HolderOrderedSet<>(extractor, varargs); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy