-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
f8b5de6
commit 769bdd6
Showing
2 changed files
with
306 additions
and
4 deletions.
There are no files selected for viewing
302 changes: 302 additions & 0 deletions
302
src/main/java/com/github/tommyettinger/ds/FilteredIterableOrderedSet.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,302 @@ | ||
/* | ||
* Copyright (c) 2022-2023 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 com.github.tommyettinger.function.ObjPredicate; | ||
import com.github.tommyettinger.function.ObjToSameFunction; | ||
import org.checkerframework.checker.nullness.qual.Nullable; | ||
|
||
import java.util.Collection; | ||
import java.util.Iterator; | ||
import java.util.Objects; | ||
|
||
/** | ||
* A customizable variant on ObjectOrderedSet that uses Iterable items made of T sub-items, and only considers a sub-item (for | ||
* equality and hashing purposes) if that sub-item satisfies a predicate. This can also edit the sub-items that pass | ||
* the filter, such as normalize their data during comparisons (and hashing). You will usually want to call | ||
* {@link #setFilter(ObjPredicate)} and/or {@link #setEditor(ObjToSameFunction)} to change the behavior of hashing and | ||
* equality before you enter any items, unless you have specified the filter and/or editor you want in the constructor. | ||
* Calling {@link #setModifiers(ObjPredicate, ObjToSameFunction)} is recommended if you need to set both the filter and | ||
* the editor; you could also set them in the constructor. | ||
* <br> | ||
* This class is related to {@link FilteredStringOrderedSet}, which can be seen as using a String as an item and the characters | ||
* of that String as its sub-items. That means this is also similar to {@link CaseInsensitiveOrderedSet}, which is essentially | ||
* a specialized version of FilteredIterableOrderedSet (which can be useful for serialization). | ||
*/ | ||
public class FilteredIterableOrderedSet<T, I extends Iterable<T>> extends ObjectOrderedSet<I> { | ||
protected ObjPredicate<T> filter = c -> true; | ||
protected ObjToSameFunction<T> editor = c -> c; | ||
/** | ||
* Creates a new set with an initial capacity of 51 and a load factor of {@link Utilities#getDefaultLoadFactor()}. | ||
* This considers all sub-items in an Iterable item and does not edit any sub-items. | ||
*/ | ||
public FilteredIterableOrderedSet () { | ||
super(); | ||
} | ||
|
||
/** | ||
* Creates a new set with the specified initial capacity and a load factor of {@link Utilities#getDefaultLoadFactor()}. | ||
* This set will hold initialCapacity items before growing the backing table. | ||
* This considers all sub-items in an Iterable item and does not edit any sub-items. | ||
* | ||
* @param initialCapacity If not a power of two, it is increased to the next nearest power of two. | ||
*/ | ||
public FilteredIterableOrderedSet (int initialCapacity) { | ||
super(initialCapacity); | ||
} | ||
|
||
/** | ||
* Creates a new set with the specified initial capacity and load factor. This set will hold initialCapacity items before | ||
* growing the backing table. | ||
* This considers all sub-items in an Iterable item and does not edit any sub-items. | ||
* | ||
* @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 FilteredIterableOrderedSet (int initialCapacity, float loadFactor) { | ||
super(initialCapacity, loadFactor); | ||
} | ||
|
||
/** | ||
* Creates a new set with an initial capacity of 51 and a load factor of {@link Utilities#getDefaultLoadFactor()}. | ||
* This uses the specified filter and editor. | ||
* | ||
* @param filter a ObjPredicate<T> that should return true iff a sub-item should be considered for equality/hashing | ||
* @param editor a ObjToSameFunction<T> that will be given a sub-item and may return a potentially different {@code T} item | ||
*/ | ||
public FilteredIterableOrderedSet (ObjPredicate<T> filter, ObjToSameFunction<T> editor) { | ||
super(); | ||
this.filter = filter; | ||
this.editor = editor; | ||
} | ||
|
||
/** | ||
* Creates a new set with the specified initial capacity and a load factor of {@link Utilities#getDefaultLoadFactor()}. | ||
* This set will hold initialCapacity items before growing the backing table. | ||
* This uses the specified filter and editor. | ||
* | ||
* @param filter a ObjPredicate<T> that should return true iff a sub-item should be considered for equality/hashing | ||
* @param editor a ObjToSameFunction<T> that will be given a sub-item and may return a potentially different {@code T} item | ||
* @param initialCapacity If not a power of two, it is increased to the next nearest power of two. | ||
*/ | ||
public FilteredIterableOrderedSet (ObjPredicate<T> filter, ObjToSameFunction<T> editor, int initialCapacity) { | ||
super(initialCapacity); | ||
this.filter = filter; | ||
this.editor = editor; | ||
} | ||
|
||
/** | ||
* Creates a new set with the specified initial capacity and load factor. This set will hold initialCapacity items before | ||
* growing the backing table. | ||
* This uses the specified filter and editor. | ||
* | ||
* @param filter a ObjPredicate<T> that should return true iff a sub-item should be considered for equality/hashing | ||
* @param editor a ObjToSameFunction<T> that will be given a sub-item and may return a potentially different {@code T} item | ||
* @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 FilteredIterableOrderedSet (ObjPredicate<T> filter, ObjToSameFunction<T> editor, int initialCapacity, float loadFactor) { | ||
super(initialCapacity, loadFactor); | ||
this.filter = filter; | ||
this.editor = editor; | ||
} | ||
|
||
/** | ||
* Creates a new set identical to the specified set. | ||
* | ||
* @param set another FilteredIterableOrderedSet to copy | ||
*/ | ||
public FilteredIterableOrderedSet (FilteredIterableOrderedSet<T, ? extends I> set) { | ||
super(set); | ||
filter = set.filter; | ||
editor = set.editor; | ||
} | ||
|
||
/** | ||
* Creates a new set that contains all distinct elements in {@code coll}. | ||
* This uses the specified filter and editor, including while it enters the items in coll. | ||
* | ||
* @param filter a ObjPredicate<T> that should return true iff a sub-item should be considered for equality/hashing | ||
* @param editor a ObjToSameFunction<T> that will be given a sub-item and may return a potentially different {@code T} item | ||
* @param coll a Collection implementation to copy, such as an ObjectList or a Set that isn't a FilteredIterableOrderedSet | ||
*/ | ||
public FilteredIterableOrderedSet (ObjPredicate<T> filter, ObjToSameFunction<T> editor, Collection<? extends I> coll) { | ||
this(filter, editor, coll.size(), Utilities.getDefaultLoadFactor()); | ||
addAll(coll); | ||
} | ||
|
||
/** | ||
* Creates a new set using {@code length} items from the given {@code array}, starting at {@code} offset (inclusive). | ||
* This uses the specified filter and editor, including while it enters the items in array. | ||
* | ||
* @param filter a ObjPredicate<T> that should return true iff a sub-item should be considered for equality/hashing | ||
* @param editor a ObjToSameFunction<T> that will be given a sub-item and may return a potentially different {@code T} item | ||
* @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 FilteredIterableOrderedSet (ObjPredicate<T> filter, ObjToSameFunction<T> editor, I[] array, int offset, int length) { | ||
this(filter, editor, length, Utilities.getDefaultLoadFactor()); | ||
addAll(array, offset, length); | ||
} | ||
|
||
/** | ||
* Creates a new set containing all the items in the given array. | ||
* This uses the specified filter and editor, including while it enters the items in array. | ||
* | ||
* @param filter a ObjPredicate<T> that should return true iff a sub-item should be considered for equality/hashing | ||
* @param editor a ObjToSameFunction<T> that will be given a sub-item and may return a potentially different {@code T} item | ||
* @param array an array that will be used in full, except for duplicate items | ||
*/ | ||
public FilteredIterableOrderedSet (ObjPredicate<T> filter, ObjToSameFunction<T> editor, I[] array) { | ||
this(filter, editor, array, 0, array.length); | ||
} | ||
|
||
public ObjPredicate<T> getFilter() { | ||
return filter; | ||
} | ||
|
||
/** | ||
* Sets the filter that determines which sub-items in an Iterable are considered for equality and hashing, then | ||
* returns this object, for chaining. ObjPredicate<T> filters could be lambdas or method references that take a | ||
* sub-item and return true if that sub-item will be used for hashing/equality, or return false to ignore it. | ||
* The default filter always returns true. If the filter changes, that invalidates anything previously entered into | ||
* this, so before changing the filter <em>this clears the entire data structure</em>, removing all existing items. | ||
* @param filter a ObjPredicate<T> that should return true iff a sub-item should be considered for equality/hashing | ||
* @return this, for chaining | ||
*/ | ||
public FilteredIterableOrderedSet<T, I> setFilter(ObjPredicate<T> filter) { | ||
clear(); | ||
this.filter = filter; | ||
return this; | ||
} | ||
|
||
public ObjToSameFunction<T> getEditor() { | ||
return editor; | ||
} | ||
|
||
/** | ||
* Sets the editor that can alter the sub-items in an Iterable item when they are being used for equality and | ||
* hashing. This does not apply any changes to the items in this data structure; it only affects how they are | ||
* hashed or compared. An editor could be a lambda or method reference; the only real requirement is that it | ||
* takes a {@code T} item and returns a {@code T} item. | ||
* The default filter returns the sub-item it is passed without changes. If the editor changes, that invalidates | ||
* anything previously entered into this, so before changing the editor <em>this clears the entire data | ||
* structure</em>, removing all existing items. | ||
* @param editor a ObjToSameFunction<T> that will be given a sub-item and may return a potentially different {@code T} item | ||
* @return this, for chaining | ||
*/ | ||
public FilteredIterableOrderedSet<T, I> setEditor(ObjToSameFunction<T> editor) { | ||
clear(); | ||
this.editor = editor; | ||
return this; | ||
} | ||
|
||
/** | ||
* Equivalent to calling {@code mySet.setFilter(filter).setEditor(editor)}, but only clears the data structure once. | ||
* @see #setFilter(ObjPredicate) | ||
* @see #setEditor(ObjToSameFunction) | ||
* @param filter a ObjPredicate<T> that should return true iff a sub-item should be considered for equality/hashing | ||
* @param editor a ObjToSameFunction<T> that will be given a sub-item and may return a potentially different {@code T} item | ||
* @return this, for chaining | ||
*/ | ||
public FilteredIterableOrderedSet<T, I> setModifiers(ObjPredicate<T> filter, ObjToSameFunction<T> editor) { | ||
clear(); | ||
this.filter = filter; | ||
this.editor = editor; | ||
return this; | ||
} | ||
|
||
protected long hashHelper(I s) { | ||
long hash = 0x9E3779B97F4A7C15L + hashMultiplier; // golden ratio | ||
for (T c : s) { | ||
if(filter.test(c)){ | ||
hash = (hash + editor.apply(c).hashCode()) * hashMultiplier; | ||
} | ||
} | ||
return hash; | ||
} | ||
|
||
@Override | ||
protected int place (Object item) { | ||
if (item instanceof Iterable) { | ||
return (int)(hashHelper((I) item) >>> shift); | ||
} | ||
return super.place(item); | ||
} | ||
|
||
@Override | ||
protected boolean equate (Object left, @Nullable Object right) { | ||
if (left == right) | ||
return true; | ||
if(right == null) return false; | ||
if ((left instanceof Iterable) && (right instanceof Iterable)) { | ||
I l = (I)left, r = (I)right; | ||
int countL = 0, countR = 0; | ||
Iterator<? extends T> i = l.iterator(), j = r.iterator(); | ||
T cl = null, cr = null; | ||
while (i.hasNext() || j.hasNext()) { | ||
if (!i.hasNext()) { | ||
cl = null; | ||
} else { | ||
boolean found = false; | ||
while (i.hasNext() && !(found = filter.test(cl = i.next()))) { | ||
cl = null; | ||
} | ||
if(found) countL++; | ||
} | ||
if (!j.hasNext()) { | ||
cr = null; | ||
} else { | ||
boolean found = false; | ||
while (j.hasNext() && !(found = filter.test(cr = j.next()))) { | ||
cr = null; | ||
} | ||
if(found) countR++; | ||
} | ||
if (!Objects.equals(cl, cr) && !Objects.equals((editor.apply(cl)), (editor.apply(cr)))) { | ||
return false; | ||
} | ||
} | ||
return countL == countR; | ||
} | ||
return false; | ||
} | ||
|
||
@Override | ||
public int hashCode () { | ||
int h = size; | ||
Object[] keyTable = this.keyTable; | ||
for (int i = 0, n = keyTable.length; i < n; i++) { | ||
Object key = keyTable[i]; | ||
if (key != null) {h += hashHelper((I)key);} | ||
} | ||
return h; | ||
} | ||
|
||
public static <T, I extends Iterable<T>> FilteredIterableOrderedSet<T, I> with (ObjPredicate<T> filter, ObjToSameFunction<T> editor, I item) { | ||
FilteredIterableOrderedSet<T, I> set = new FilteredIterableOrderedSet<>(filter, editor, 1, Utilities.getDefaultLoadFactor()); | ||
set.add(item); | ||
return set; | ||
} | ||
|
||
@SafeVarargs | ||
public static <T, I extends Iterable<T>> FilteredIterableOrderedSet<T, I> with (ObjPredicate<T> filter, ObjToSameFunction<T> editor, I... array) { | ||
return new FilteredIterableOrderedSet<>(filter, editor, array); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters