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

com.github.tommyettinger.ds.ObjectOrderedSet 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 static com.github.tommyettinger.ds.Utilities.tableSize;

/**
 * A {@link ObjectSet} that also stores keys in an {@link ObjectList} using the insertion order. Null keys are not allowed. No
 * allocation is done except when growing the table size.
 * 

* {@link #iterator() Iteration} is ordered and faster than an unordered set. Keys 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. Load factors greater than 0.9 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 * ObjectOrderedSet and ObjectObjectOrderedMap. *

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

* 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; it just works more slowly in that case. * * @author Nathan Sweet * @author Tommy Ettinger */ public class ObjectOrderedSet extends ObjectSet implements Ordered { protected final ObjectList items; public ObjectOrderedSet () { items = new ObjectList<>(); } public ObjectOrderedSet (int initialCapacity, float loadFactor) { super(initialCapacity, loadFactor); items = new ObjectList<>(initialCapacity); } public ObjectOrderedSet (int initialCapacity) { super(initialCapacity); 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 ObjectOrderedSet (Iterator coll) { this(); addAll(coll); } public ObjectOrderedSet (ObjectOrderedSet set) { super(set); items = new ObjectList<>(set.items); } /** * Creates a new set that contains all distinct elements in {@code coll}. */ public ObjectOrderedSet (Collection coll) { this(coll.size()); addAll(coll); } /** * Creates a new set using {@code length} items from the given {@code array}, starting at {@code} offset (inclusive). * * @param array an array to draw items from * @param offset the first index in array to draw an item from * @param length how many items to take from array; bounds-checking is the responsibility of the using code */ public ObjectOrderedSet (T[] array, int offset, int length) { this(length); addAll(array, offset, length); } /** * Creates a new set that contains all distinct elements in {@code items}. * * @param items an array that will be used in full, except for duplicate items */ public ObjectOrderedSet (T[] items) { this(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. * * @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 ObjectOrderedSet (Ordered other, int offset, int count) { this(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; } /** * 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; } @Override public boolean remove (@NonNull Object key) { return super.remove(key) && items.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 key = items.removeAt(index); if(key != null) super.remove(key); return key; } /** * 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(after)) {return false;} if (!super.remove(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(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 T first () { if (size == 0) throw new IllegalStateException("ObjectOrderedSet is empty."); return items.first(); } @Override public void clear (int maximumCapacity) { items.clear(); super.clear(maximumCapacity); } @Override public void clear () { items.clear(); super.clear(); } /** * Gets the ObjectList of 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 items, in iteration order (usually insertion-order), that this uses */ @Override public ObjectList order () { return items; } /** * Sorts this ObjectOrderedSet in-place by the keys' 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 keys. If {@code comp} is null, then this * will sort by the natural ordering of the keys, which requires {@code T} to {@link Comparable}. * * @param comp a Comparator that can compare two {@code T} keys, or null to use the keys' 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 ObjectOrderedSetIterator#ObjectOrderedSetIterator(ObjectOrderedSet)} to nest iterators. * * @return an {@link Iterator} over the T items in this, in order */ @Override public @NonNull ObjectSetIterator iterator () { if (iterator1 == null || iterator2 == null) { iterator1 = new ObjectOrderedSetIterator<>(this); iterator2 = new ObjectOrderedSetIterator<>(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 ObjectOrderedSetIterator extends ObjectSetIterator { private final ObjectList items; public ObjectOrderedSetIterator (ObjectOrderedSet set) { super(set); items = set.items; } @Override public void reset () { nextIndex = 0; hasNext = set.size > 0; } @Override public K next () { if (!hasNext) {throw new NoSuchElementException();} if (!valid) {throw new RuntimeException("#iterator() cannot be used nested.");} K 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--; set.remove(items.get(nextIndex)); } } /** * Constructs an empty set given the type as a generic type argument. * 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 the type of items; must be given explicitly * @return a new set containing nothing */ public static ObjectOrderedSet with () { return new ObjectOrderedSet<>(0); } /** * Creates a new ObjectOrderedSet that holds only the given item, but can be resized. * @param item one T item * @return a new ObjectOrderedSet that holds the given item * @param the type of item, typically inferred */ public static ObjectOrderedSet with (T item) { ObjectOrderedSet set = new ObjectOrderedSet<>(1); set.add(item); return set; } /** * Creates a new ObjectOrderedSet that holds only the given items, but can be resized. * @param item0 a T item * @param item1 a T item * @return a new ObjectOrderedSet that holds the given items * @param the type of item, typically inferred */ public static ObjectOrderedSet with (T item0, T item1) { ObjectOrderedSet set = new ObjectOrderedSet<>(2); set.add(item0, item1); return set; } /** * Creates a new ObjectOrderedSet that holds only the given items, but can be resized. * @param item0 a T item * @param item1 a T item * @param item2 a T item * @return a new ObjectOrderedSet that holds the given items * @param the type of item, typically inferred */ public static ObjectOrderedSet with (T item0, T item1, T item2) { ObjectOrderedSet set = new ObjectOrderedSet<>(3); set.add(item0, item1, item2); return set; } /** * Creates a new ObjectOrderedSet that holds only the given items, but can be resized. * @param item0 a T item * @param item1 a T item * @param item2 a T item * @param item3 a T item * @return a new ObjectOrderedSet that holds the given items * @param the type of item, typically inferred */ public static ObjectOrderedSet with (T item0, T item1, T item2, T item3) { ObjectOrderedSet set = new ObjectOrderedSet<>(4); set.add(item0, item1, item2, item3); return set; } /** * Creates a new ObjectOrderedSet that holds only the given items, but can be resized. * @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 ObjectOrderedSet that holds the given items * @param the type of item, typically inferred */ public static ObjectOrderedSet with (T item0, T item1, T item2, T item3, T item4) { ObjectOrderedSet set = new ObjectOrderedSet<>(5); set.add(item0, item1, item2, item3); set.add(item4); return set; } /** * Creates a new ObjectOrderedSet that holds only the given items, but can be resized. * @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 ObjectOrderedSet that holds the given items * @param the type of item, typically inferred */ public static ObjectOrderedSet with (T item0, T item1, T item2, T item3, T item4, T item5) { ObjectOrderedSet set = new ObjectOrderedSet<>(6); set.add(item0, item1, item2, item3); set.add(item4, item5); return set; } /** * Creates a new ObjectOrderedSet that holds only the given items, but can be resized. * @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 ObjectOrderedSet that holds the given items * @param the type of item, typically inferred */ public static ObjectOrderedSet with (T item0, T item1, T item2, T item3, T item4, T item5, T item6) { ObjectOrderedSet set = new ObjectOrderedSet<>(7); set.add(item0, item1, item2, item3); set.add(item4, item5, item6); return set; } /** * Creates a new ObjectOrderedSet that holds only the given items, but can be resized. * @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 ObjectOrderedSet that holds the given items * @param the type of item, typically inferred */ public static ObjectOrderedSet with (T item0, T item1, T item2, T item3, T item4, T item5, T item6, T item7) { ObjectOrderedSet set = new ObjectOrderedSet<>(8); set.add(item0, item1, item2, item3); set.add(item4, item5, item6, item7); return set; } /** * Creates a new ObjectOrderedSet 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 varargs a T varargs or T array; remember that varargs allocate * @return a new ObjectOrderedSet that holds the given items * @param the type of item, typically inferred */ @SafeVarargs public static ObjectOrderedSet with (T... varargs) { return new ObjectOrderedSet<>(varargs); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy