/*license*\
XBN-Java: Copyright (C) 2014, Jeff Epstein (aliteralmind __DASH__ github __AT__ yahoo __DOT__ com)
This software is dual-licensed under the:
- Lesser General Public License (LGPL) version 3.0 or, at your option, any later version;
- Apache Software License (ASL) version 2.0.
Either license may be applied at your discretion. More information may be found at
- http://en.wikipedia.org/wiki/Multi-licensing.
The text of both licenses is available in the root directory of this project, under the names "LICENSE_lgpl-3.0.txt" and "LICENSE_asl-2.0.txt". The latest copies may be downloaded at:
- LGPL 3.0: https://www.gnu.org/licenses/lgpl-3.0.txt
- ASL 2.0: http://www.apache.org/licenses/LICENSE-2.0.txt
\*license*/
package com.github.xbn.array;
import com.github.xbn.lang.ToStringAppendable;
import com.github.xbn.lang.CrashIfObject;
import com.github.xbn.lang.Copyable;
import com.github.xbn.number.NumberUtil;
/**
<p>Manages a binary search. This only works with indexed and sorted containers, such as {@code List}-s and arrays (containing elements which are, ideally, unique).</p>
{@.codelet.and.out com.github.xbn.examples.array.BinarySearcherXmpl%eliminateCommentBlocksAndPackageDecl()}
<p>Referring to the container-being-searched, the binary-search <i>data</i> is: <ul>
<li>The <b>{@link #getIndexLeft() minimum} and {@link #getIndexRightX() maximum} index bounds:</b> The element-range in which the desired element exists (or would exist given the current ordering). This range is progressively narrowed until the search is complete.</li>
<li>The <b>{@link #getIndexMiddle() middle} index:</b> The next element index to be searched.</li>
<li>The <b>{@link #isOrderAsc() sort-order} direction</b></li>
</ul></p>
* @since 0.1.0
* @author Copyright (C) 2014, Jeff Epstein ({@code aliteralmind __DASH__ github __AT__ yahoo __DOT__ com}), dual-licensed under the LGPL (version 3.0 or later) or the ASL (version 2.0). See source code for details. <a href="http://xbnjava.aliteralmind.com">{@code http://xbnjava.aliteralmind.com}</a>, <a href="https://github.com/aliteralmind/xbnjava">{@code https://github.com/aliteralmind/xbnjava}</a>
**/
public class BinarySearcher implements Copyable, ToStringAppendable {
//config
private int iArrayLength = -1;
private boolean bOrderAsc = false;
//state
private int ixL = -1;
private int ixxRight = -1;
private int ixM = -1;
private int ixMPrev = -1;
private int ix2Ins = -1;
//constructors...START
public BinarySearcher() {
this(true);
}
/**
<p>Create a new {@code BinarySearcher}.</p>
* @param is_ascending If {@code true}, the container is ordered ascending. Get with {@link #isOrderAsc() isOrderAsc}{@code ()}.
* @see #BinarySearcher(BinarySearcher) this(bs)
*/
public BinarySearcher(boolean is_ascending) {
bOrderAsc = is_ascending;
resetState();
}
/**
<p>Create a new {@code BinarySearcher} as a duplicate of another.</p>
* @param to_copy May not be {@code null}.
* @see #getObjectCopy()
*/
public BinarySearcher(BinarySearcher to_copy) {
try {
iArrayLength = to_copy.iArrayLength;
} catch(RuntimeException rx) {
throw CrashIfObject.nullOrReturnCause(to_copy, "to_copy", null, rx);
}
bOrderAsc = to_copy.bOrderAsc;
ixL = to_copy.ixL;
ixxRight = to_copy.ixxRight;
ixM = to_copy.ixM;
ixMPrev = to_copy.ixMPrev;
ix2Ins = to_copy.ix2Ins;
}
//constructors...END
//setters...START
/**
<p>Declare a new container to be searched, and the order direction.</p>
* @param cntr_length The length of the container to be searched. May not be less than one. Get with {@link #getContainerLength() getContainerLength}{@code ()}.
* @return {@link #resetState() resetState}{@code ()}
* @see #containerLength(int) containerLength(i)
*/
public BinarySearcher containerLength(int cntr_length) {
if(cntr_length < 1) {
throw new IllegalArgumentException("cntr_length (" + cntr_length + ") is less than one.");
}
iArrayLength = cntr_length;
return resetState();
}
/**
<p>Prepare for a new search of the current container (container length is unchanged).</p>
<p>This sets<ol>
<li>{@link #getIndexLeft() getIndexLeft}{@code ()} to {@code 0}</li>
<li>{@link #getIndexMiddle() getIndexMiddle}{@code ()} to
<br/> <code>[{@link #getContainerLength() getContainerLength}() - 1]</code></li>
<li>{@link #getIndexMiddle() getIndexMiddle}{@code ()} to
<br/> <code>{@link com.github.xbn.number.NumberUtil NumberUtil}.{@link com.github.xbn.number.NumberUtil#getMiddleInt(int, int) getMiddleInt}(0, getIndexMiddle())</code></li>
<li>Both {@link #getIndexMiddlePrev() getIndexMiddlePrev}{@code ()} and {@link #getIndexInsertAt() getIndexInsertAt}{@code ()} to {@code -1}</li>
</ol></p>
* @return <i>{@code this}</i>
*/
public BinarySearcher resetState() {
ixL = 0;
ixxRight = getContainerLength() - 1;
ixM = NumberUtil.getMiddleInt(ixL, ixxRight);
ixMPrev = -1;
ix2Ins = -1;
return this;
}
//setters...END
//getters...START
/**
<p>Get the left-most index that has not yet been searched.</p>
* @return As initialized by {@link #resetState() resetState}{@code ()} or the {@link #BinarySearcher(BinarySearcher) copy-constructor}, or as updated by {@link #resetIteration(boolean) resetIteration(b)}
* @see #getContainerLength()
*/
public final int getIndexLeft() {
return ixL;
}
/**
<p>Get the next index to analyze.</p>
* @return As initialized by {@link #resetState() resetState}{@code ()} or the {@link #BinarySearcher(BinarySearcher) copy-constructor}, or as updated by {@link #resetIteration(boolean) resetIteration(b)}
* @see #getContainerLength()
*/
public final int getIndexMiddle() {
return ixM;
}
/**
<p>Get the next index to analyze.</p>
* @return As initialized by {@link #resetState() resetState}{@code ()} or the {@link #BinarySearcher(BinarySearcher) copy-constructor}, or as updated by {@link #resetIteration(boolean) resetIteration(b)}
* @see #getContainerLength()
*/
public final int getIndexMiddlePrev() {
return ixMPrev;
}
/**
<p>Get the next index to analyze.</p>
* @return As initialized by {@link #resetState() resetState}{@code ()} or the {@link #BinarySearcher(BinarySearcher) copy-constructor}, or as updated by {@link #resetIteration(boolean) resetIteration(b)}
* @see #getContainerLength()
*/
public final int getIndexInsertAt() {
return ix2Ins;
}
/**
<p>Get the right-most index that has not yet been searched.</p>
* @return As initialized by {@link #resetState() resetState}{@code ()} or the {@link #BinarySearcher(BinarySearcher) copy-constructor}, or as updated by {@link #resetIteration(boolean) resetIteration(b)}
* @see #getContainerLength()
*/
public final int getIndexRightX() {
return ixxRight;
}
/**
<p>How many elements are in the container-being-searched?.</p>
<p><i><b>See:</b><ul>
<li>Configuration: <i>This function</i> and {@link #isOrderAsc() isOrderAsc}{@code ()}</li>
<li>State: {@link #getIndexInsertAt() getIndexInsertAt}{@code ()}, {@link #getIndexLeft() getIndexLeft}{@code ()}, {@link #getIndexMiddle() getIndexMiddle}{@code ()}, {@link #getIndexMiddlePrev() getIndexMiddlePrev}{@code ()}, {@link #getIndexRightX() getIndexRightX}{@code ()}</li>
</ul></i></p>
* @see #containerLength(int)
*/
public final int getContainerLength() {
return iArrayLength;
}
/**
<p>How are the elements in the container-being-searhed ordered?.</p>
* @see #BinarySearcher(boolean) this(b)
* @see #getContainerLength()
*/
public final boolean isOrderAsc() {
return bOrderAsc;
}
//getters...END
//other...START
/**
<p>Set the indexes for the next search.</p>
<p><i><b>See:</b><ul>
<li>{@link #isDone() isDone}{@code ()}, {@link #getIndexInsertAtN1M1() getIndexInsertAtN1M1}{@code ()}</li>
</ul></i></p>
<p><i>If this is behaving strangely, perhaps the {@link #isOrderAsc() order-direction} is not as expected.</i></p>
* @param was_itemToFindLessThanMiddleElement Was the search item less or greater than the element just compared against? If {@code true}: less.
* @exception IllegalStateException If {@link #isDone() isDone}{@code ()} is {@code true}.
*/
public final void resetIteration(boolean was_itemToFindLessThanMiddleElement) {
if(isDone()) {
throw new IllegalStateException("isDone() is true.");
}
if(was_itemToFindLessThanMiddleElement) {
//To-search-for is less than the middle elemnt.
//ixL stays the same.
ixxRight = (getIndexMiddle() + (isOrderAsc() ? -1 : 1));
} else {
//To-search-for is greater than middle element.
ixL = getIndexMiddle() + (isOrderAsc() ? 1 : -1);
//ixxRight stays the same.;
}
ixMPrev = getIndexMiddle(); //ixM
if(getIndexLeft() >= 0 && getIndexLeft() <= getIndexRightX()) {
ixM = NumberUtil.getMiddleInt(getIndexLeft(), getIndexRightX());
} else {
ix2Ins = ixMPrev + (was_itemToFindLessThanMiddleElement
? (isOrderAsc() ? 0 : 1)
: (isOrderAsc() ? 1 : 0));
}
}
/**
<p>Is the search over?.</p>
* @return <code>({@link #getIndexMiddlePrev() getIndexMiddlePrev}() == {@link #getIndexMiddle() getIndexMiddle}() || getIndexMiddle() < {@link #getIndexLeft() getIndexLeft}())</code>
* @return {@code true} If you were with me all the while.
* @see #resetIteration(boolean) resetIteration(b)
*/
public final boolean isDone() {
return (getIndexMiddlePrev() == getIndexMiddle() || getIndexMiddle() < getIndexLeft());
}
/**
<p>Utility: Get the negative index at which to insert, minus 1. This can be used to indicate that an element needs to be inserted at a certain index <i>and does not yet exist in the container.</i></p>
* @return <code>(({@link #getIndexInsertAt() getIndexInsertAt}() * -1) - 1)</code>
* @see #resetIteration(boolean) resetIteration(b)
*/
public final int getIndexInsertAtN1M1() {
return ((getIndexInsertAt() * -1) - 1);
}
public String toString() {
return appendToString(new StringBuilder()).toString();
}
public StringBuilder appendToString(StringBuilder to_appendTo) {
try {
to_appendTo.append(this.getClass().getName());
} catch(RuntimeException rx) {
throw CrashIfObject.nullOrReturnCause(to_appendTo, "to_appendTo", null, rx);
}
to_appendTo.append("getContainerLength()=").append(getContainerLength()).
append(", isOrderAsc()=").append(isOrderAsc()).
append(", left/middle/right indexes=[").
append(getIndexLeft()).append(", ").
append(getIndexMiddle()).append(", ").
append(getIndexRightX()).
append("], getIndexInsertAt()=").append(getIndexInsertAt());
return to_appendTo;
}
/**
<p>Get a complete copy of this {@code BinarySearcher}.</p>
* @return <code>(new {@link #BinarySearcher(BinarySearcher) BinarySearcher}(this))</code>
*/
public BinarySearcher getObjectCopy() {
return (new BinarySearcher(this));
}
//other...END
}