diff --git a/Dorkbox-Util/src/dorkbox/util/collections/Bias.java b/Dorkbox-Util/src/dorkbox/util/collections/Bias.java new file mode 100644 index 0000000..a0f474e --- /dev/null +++ b/Dorkbox-Util/src/dorkbox/util/collections/Bias.java @@ -0,0 +1,54 @@ +/* + * The MIT License + * + * Copyright 2013 Tim Boudreau. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package dorkbox.util.collections; + +/** + * Bias used to decide how to resolve ambiguity, for example in a binary search + * with a list of timestamps - if the requested timestamp lies between two + * actual data snapshots, should we return the next, previous, nearest one, or + * none unless there is an exact match. + * + * @author Tim Boudreau + */ +public enum Bias { + + /** + * If a search result falls between two elements, prefer the next element + */ + FORWARD, + /** + * If a search result falls between two elements, prefer the previous element + */ + BACKWARD, + /** + * If a search result falls between two elements, prefer the element with + * the minimum distance + */ + NEAREST, + /** + * If a search result falls between two elements, return no element unless + * there is an exact match + */ + NONE; +} \ No newline at end of file diff --git a/Dorkbox-Util/src/dorkbox/util/collections/BinarySearch.java b/Dorkbox-Util/src/dorkbox/util/collections/BinarySearch.java new file mode 100644 index 0000000..2b6a112 --- /dev/null +++ b/Dorkbox-Util/src/dorkbox/util/collections/BinarySearch.java @@ -0,0 +1,190 @@ +/* + * The MIT License + * + * Copyright 2013 Tim Boudreau. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package dorkbox.util.collections; + +import java.util.List; + +/** + * General-purpose binary search algorithm; you pass in an array or list + * wrapped in an instance of Indexed, and an + * Evaluator which converts the contents of the list into + * numbers used by the binary search algorithm. Note that the data + * (as returned by the Indexed array/list) must be in order from + * low to high. The indices need not be contiguous (presumably + * they are not or you wouldn't be using this class), but they must be + * sorted. If assertions are enabled, this is enforced; if not, very + * bad things (endless loops, etc.) can happen as a consequence of passing + * unsorted data in. + *

+ * This class is not thread-safe and the size and contents of the Indexed + * should not change while a search is being performed. + * + * @author Tim Boudreau + */ +public class BinarySearch { + + private final Evaluator eval; + private final Indexed indexed; + + /** + * Create a new binary search. + * + * @param eval The thing which converts elements into numbers + * @param indexed A collection, list or array + */ + public BinarySearch(Evaluator eval, Indexed indexed) { + this.eval = eval; + this.indexed = indexed; + assert checkSorted(); + } + + public BinarySearch(Evaluator eval, List l) { + this (eval, new ListWrap(l)); + } + + private boolean checkSorted() { + long val = Long.MIN_VALUE; + long sz = this.indexed.size(); + for (long i=0; i < sz; i++) { + T t = this.indexed.get(i); + long nue = this.eval.getValue(t); + if (val != Long.MIN_VALUE) { + if (nue < val) { + throw new IllegalArgumentException("Collection is not sorted at " + i + " - " + this.indexed); + } + } + val = nue; + } + return true; + } + + public long search(long value, Bias bias) { + return search(0, this.indexed.size()-1, value, bias); + } + + public T match(T prototype, Bias bias) { + long value = this.eval.getValue(prototype); + long index = search(value, bias); + return index == -1 ? null : this.indexed.get(index); + } + + public T searchFor(long value, Bias bias) { + long index = search(value, bias); + return index == -1 ? null : this.indexed.get(index); + } + + private long search(long start, long end, long value, Bias bias) { + long range = end - start; + if (range == 0) { + return start; + } + if (range == 1) { + T ahead = this.indexed.get(end); + T behind = this.indexed.get(start); + long v1 = this.eval.getValue(behind); + long v2 = this.eval.getValue(ahead); + switch (bias) { + case BACKWARD: + return start; + case FORWARD: + return end; + case NEAREST: + if (v1 == value) { + return start; + } else if (v2 == value) { + return end; + } else { + if (Math.abs(v1 - value) < Math.abs(v2 - value)) { + return start; + } else { + return end; + } + } + case NONE: + if (v1 == value) { + return start; + } else if (v2 == value) { + return end; + } else { + return -1; + } + default: + throw new AssertionError(bias); + + } + } + long mid = start + range / 2; + long vm = this.eval.getValue(this.indexed.get(mid)); + if (value >= vm) { + return search(mid, end, value, bias); + } else { + return search(start, mid, value, bias); + } + } + + /** + * Converts an object into a numeric value that is used to + * perform binary search + * @param + */ + public interface Evaluator { + + public long getValue(T obj); + } + + /** + * Abstraction for list-like things which have a length and indices + * @param + */ + public interface Indexed { + + public T get(long index); + + public long size(); + } + + private static final class ListWrap implements Indexed { + + private final List l; + + ListWrap(List l) { + this.l = l; + } + + @Override + public T get(long index) { + return this.l.get((int) index); + } + + @Override + public long size() { + return this.l.size(); + } + + @Override + public String toString() { + return super.toString() + '{' + this.l + '}'; + } + } +} \ No newline at end of file diff --git a/Dorkbox-Util/src/dorkbox/util/collections/ConcurrentHashMapV8.java b/Dorkbox-Util/src/dorkbox/util/collections/ConcurrentHashMapV8.java index 98b4ab8..c2be6fd 100644 --- a/Dorkbox-Util/src/dorkbox/util/collections/ConcurrentHashMapV8.java +++ b/Dorkbox-Util/src/dorkbox/util/collections/ConcurrentHashMapV8.java @@ -3,7 +3,7 @@ * Expert Group and released to the public domain, as explained at * http://creativecommons.org/publicdomain/zero/1.0/ */ -package dorkbox.util; +package dorkbox.util.collections; import java.io.ObjectStreamField; import java.io.Serializable; diff --git a/Dorkbox-Util/src/dorkbox/util/collections/IntArray.java b/Dorkbox-Util/src/dorkbox/util/collections/IntArray.java index 2b202d4..aae26f0 100644 --- a/Dorkbox-Util/src/dorkbox/util/collections/IntArray.java +++ b/Dorkbox-Util/src/dorkbox/util/collections/IntArray.java @@ -16,7 +16,7 @@ // from libGDX -package dorkbox.util.primativeCollections; +package dorkbox.util.collections; import java.util.Arrays; diff --git a/Dorkbox-Util/src/dorkbox/util/collections/IntMap.java b/Dorkbox-Util/src/dorkbox/util/collections/IntMap.java index b671c6b..5b306bd 100644 --- a/Dorkbox-Util/src/dorkbox/util/collections/IntMap.java +++ b/Dorkbox-Util/src/dorkbox/util/collections/IntMap.java @@ -16,7 +16,7 @@ // slightly tweaked from libGDX -package dorkbox.util.primativeCollections; +package dorkbox.util.collections; import java.util.Iterator; diff --git a/Dorkbox-Util/src/dorkbox/util/collections/ObjectIntMap.java b/Dorkbox-Util/src/dorkbox/util/collections/ObjectIntMap.java index 5665bcf..3c35848 100644 --- a/Dorkbox-Util/src/dorkbox/util/collections/ObjectIntMap.java +++ b/Dorkbox-Util/src/dorkbox/util/collections/ObjectIntMap.java @@ -1,5 +1,5 @@ -package dorkbox.util.primativeCollections; +package dorkbox.util.collections; import com.esotericsoftware.kryo.util.ObjectMap;