/* * 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.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 + '}'; } } }