com.hfg.math.Range Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of com_hfg Show documentation
Show all versions of com_hfg Show documentation
com.hfg xml, html, svg, and bioinformatics utility library
package com.hfg.math;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import com.hfg.exception.ProgrammingException;
import com.hfg.util.CompareUtil;
import com.hfg.util.collection.CollectionUtil;
//------------------------------------------------------------------------------
/**
Generic range object.
@author J. Alex Taylor, hairyfatguy.com
*/
//------------------------------------------------------------------------------
// com.hfg Library
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com
// [email protected]
//------------------------------------------------------------------------------
public class Range implements Cloneable, Comparable>
{
private T mStart;
private T mEnd;
// Cached values
private Boolean mEndValueIsExclusive;
//###########################################################################
// CONSTRUCTORS
//###########################################################################
//--------------------------------------------------------------------------
public Range()
{
}
//--------------------------------------------------------------------------
public Range(T inStart, T inEnd)
{
mStart = inStart;
mEnd = inEnd;
}
//###########################################################################
// PUBLIC METHODS
//###########################################################################
//--------------------------------------------------------------------------
public Range setStart(T inValue)
{
mStart = inValue;
return this;
}
//--------------------------------------------------------------------------
public T getStart()
{
return mStart;
}
//--------------------------------------------------------------------------
public Range setEnd(T inValue)
{
mEnd = inValue;
return this;
}
//--------------------------------------------------------------------------
public T getEnd()
{
return mEnd;
}
//--------------------------------------------------------------------------
@Override
public String toString()
{
return String.format("[%s, %s]",
getStart() != null ? getStart().toString() : null,
getEnd() != null ? getEnd().toString() : null);
}
//---------------------------------------------------------------------------
@Override
public Range clone()
{
Range cloneObj;
try
{
cloneObj = (Range) super.clone();
}
catch (CloneNotSupportedException e)
{
throw new ProgrammingException(e);
}
return cloneObj;
}
//--------------------------------------------------------------------------
@Override
public int hashCode()
{
int hashCode = (getStart() != null ? getStart().hashCode() : 0);
hashCode += 31 * (getEnd() != null ? getEnd().hashCode() : 0);
return hashCode;
}
//--------------------------------------------------------------------------
@Override
public boolean equals(Object inObj2)
{
boolean result = false;
if (inObj2 != null
&& inObj2 instanceof Range)
{
result = (0 == compareTo((Range)inObj2));
}
return result;
}
//--------------------------------------------------------------------------
@Override
public int compareTo(Range inObj2)
{
int result = 0;
if (null == inObj2)
{
result = 1;
}
else
{
// Some custom work here to ensure that null starts sort first and
// null ends sort last
if (getStart() != null)
{
if (inObj2.getStart() != null)
{
result = CompareUtil.compare(getStart(), inObj2.getStart());
}
}
else if (inObj2.getStart() != null)
{
result = -1;
}
if (0 == result)
{
if (getEnd() != null)
{
if (inObj2.getEnd() != null)
{
result = CompareUtil.compare(getEnd(), inObj2.getEnd());
}
}
else if (inObj2.getEnd() != null)
{
result = 1;
}
}
}
return result;
}
//--------------------------------------------------------------------------
public Double length()
{
Double length = null;
if (getStart() != null
&& getEnd() != null)
{
length = getEnd().doubleValue() - getStart().doubleValue();
// Is the end value inclusive?
if (! endValueIsExclusive())
{
length += 1;
}
}
return length;
}
//--------------------------------------------------------------------------
public boolean contains(Number inValue)
{
return ((inValue != null
&& ((getStart() != null
&& inValue.doubleValue() >= getStart().doubleValue()
&& getEnd() != null
&& (inValue.doubleValue() < getEnd().doubleValue()
|| (! endValueIsExclusive()
&& inValue.doubleValue() == getEnd().doubleValue())))
|| (null == getStart()
&& getEnd() != null
&& (inValue.doubleValue() < getEnd().doubleValue()
|| (! endValueIsExclusive()
&& inValue.doubleValue() == getEnd().doubleValue())))
|| (null == getEnd()
&& getStart() != null
&& inValue.doubleValue() >= getStart().doubleValue()))
|| (null == getStart()
&& null == getEnd()))
|| (null == getStart()
&& null == getEnd()));
}
//--------------------------------------------------------------------------
public boolean contains(int inValue)
{
return ((getStart() != null
&& inValue >= getStart().doubleValue()
&& getEnd() != null
&& (inValue < getEnd().doubleValue()
|| (! endValueIsExclusive()
&& inValue == getEnd().doubleValue())))
|| (null == getStart()
&& getEnd() != null
&& (inValue < getEnd().doubleValue()
|| (! endValueIsExclusive()
&& inValue == getEnd().doubleValue())))
|| (null == getEnd()
&& getStart() != null
&& inValue >= getStart().doubleValue())
|| (null == getStart()
&& null == getEnd()));
}
//--------------------------------------------------------------------------
public boolean contains(float inValue)
{
return ((getStart() != null
&& inValue >= getStart().doubleValue()
&& getEnd() != null
&& (inValue < getEnd().doubleValue()
|| (! endValueIsExclusive()
&& inValue == getEnd().doubleValue())))
|| (null == getStart()
&& getEnd() != null
&& (inValue < getEnd().doubleValue()
|| (! endValueIsExclusive()
&& inValue == getEnd().doubleValue())))
|| (null == getEnd()
&& getStart() != null
&& inValue >= getStart().doubleValue())
|| (null == getStart()
&& null == getEnd()));
}
//--------------------------------------------------------------------------
public boolean contains(double inValue)
{
return ((getStart() != null
&& inValue >= getStart().doubleValue()
&& getEnd() != null
&& (inValue < getEnd().doubleValue()
|| (! endValueIsExclusive()
&& inValue == getEnd().doubleValue())))
|| (null == getStart()
&& getEnd() != null
&& (inValue < getEnd().doubleValue()
|| (! endValueIsExclusive()
&& inValue == getEnd().doubleValue())))
|| (null == getEnd()
&& getStart() != null
&& inValue >= getStart().doubleValue())
|| (null == getStart()
&& null == getEnd()));
}
//--------------------------------------------------------------------------
public boolean contains(Range inRange2)
{
return (contains(inRange2.getStart().doubleValue())
&& contains(inRange2.getEnd().doubleValue()));
}
//---------------------------------------------------------------------------
/**
Collapses a collection of specified ranges.
Ex: given three ranges: [1,4], [2,10], and [14,15] the result would be two ranges: [1,10] and [14,15].
@param inOrigList a collection of Ranges to be combined.
@return a list of Ranges that represents the union of the specified Ranges.
*/
public static List> union(Collection> inOrigList)
{
List> condensedRanges = new ArrayList<>(inOrigList);
boolean overlap = true;
while (overlap)
{
overlap = false;
for (int i = 0; i < condensedRanges.size() - 1; i++)
{
Range range1 = condensedRanges.get(i);
for (int j = i + 1; j < condensedRanges.size(); j++)
{
Range range2 = condensedRanges.get(j);
if (range1.intersects(range2)
|| (! range1.endValueIsExclusive()
&& ((range1.getStart() != null
&& range2.getEnd() != null
&& 1 == range1.getStart().longValue() - range2.getEnd().longValue())
|| (range1.getEnd() != null
&& range2.getStart() != null
&& 1 == range2.getStart().longValue() - range1.getEnd().longValue()))))
{
T condensedStart = null;
if (range1.getStart() != null
&& range2.getStart() != null)
{
int startComparison = CompareUtil.compare(range1.getStart(), range2.getStart());
condensedStart = startComparison > 0 ? range2.getStart() : range1.getStart();
}
T condensedEnd = null;
if (range1.getEnd() != null
&& range2.getEnd() != null)
{
int endComparison = CompareUtil.compare(range1.getEnd(), range2.getEnd());
condensedEnd = endComparison > 0 ? range1.getEnd() : range2.getEnd();
}
Range condensedRange = new Range(condensedStart, condensedEnd);
condensedRanges.remove(j);
condensedRanges.remove(i);
condensedRanges.add(condensedRange);
overlap = true;
break;
}
}
if (overlap)
{
break;
}
}
}
Collections.sort(condensedRanges);
return condensedRanges;
}
//---------------------------------------------------------------------------
/**
Returns a single Range that encompasses the collection of input ranges and ignores internal gaps.
@param inOrigList a collection of input ranges
@return a single Range that encompasses the input ranges and ignores internal gaps
*/
public static Range superUnion(Collection> inOrigList)
{
Range superUnion = null;
if (CollectionUtil.hasValues(inOrigList))
{
for (Range range : inOrigList)
{
if (null == superUnion)
{
superUnion = range;
}
else
{
superUnion = superUnion.superUnion(range);
}
}
}
return superUnion;
}
//---------------------------------------------------------------------------
/**
Returns a single Range that encompasses both input ranges and ignores internal gaps.
@param inRange2 the second Range to union with the current range
@return a single Range that encompasses both input ranges and ignores internal gaps
*/
public Range superUnion(Range inRange2)
{
T start = null;
T end = null;
if (inRange2 != null)
{
if (getStart() != null
&& inRange2.getStart() != null)
{
start = (getStart().doubleValue() < inRange2.getStart().doubleValue() ? getStart() : inRange2.getStart());
}
if (getEnd() != null
&& inRange2.getEnd() != null)
{
end = (getEnd().doubleValue() > inRange2.getEnd().doubleValue() ? getEnd() : inRange2.getEnd());
}
}
return new Range<>(start, end);
}
//---------------------------------------------------------------------------
public List> subtract(Range inRange2)
{
List> resultRanges = new ArrayList<>(2);
Range intersection = intersection(inRange2);
if (intersection != null)
{
// Right chunk?
Range leftChunk;
if (null == getStart())
{
if (intersection.getStart() != null)
{
leftChunk = new Range<>();
if (endValueIsExclusive())
{
leftChunk.setEnd(intersection.getStart());
}
else
{
if (intersection.getStart() instanceof Long)
{
leftChunk.setEnd((T) new Long(intersection.getStart().longValue() - 1));
}
else if (intersection.getStart() instanceof Integer)
{
leftChunk.setEnd((T) new Integer(intersection.getStart().intValue() - 1));
}
}
resultRanges.add(leftChunk);
}
}
else if (CompareUtil.compare(intersection.getStart(), getStart()) > 0)
{
leftChunk = new Range<>();
leftChunk.setStart(getStart());
if (endValueIsExclusive())
{
leftChunk.setEnd(intersection.getStart());
}
else
{
if (intersection.getStart() instanceof Long)
{
leftChunk.setEnd((T) new Long(intersection.getStart().longValue() - 1));
}
else if (intersection.getStart() instanceof Integer)
{
leftChunk.setEnd((T) new Integer(intersection.getStart().intValue() - 1));
}
}
resultRanges.add(leftChunk);
}
// Right chunk?
Range rightChunk;
if (null == getEnd())
{
if (intersection.getEnd() != null)
{
rightChunk = new Range<>();
if (endValueIsExclusive())
{
rightChunk.setStart(intersection.getEnd());
}
else
{
if (intersection.getEnd() instanceof Long)
{
rightChunk.setStart((T) new Long(intersection.getEnd().longValue() + 1));
}
else if (intersection.getEnd() instanceof Integer)
{
rightChunk.setStart((T) new Integer(intersection.getEnd().intValue() + 1));
}
}
resultRanges.add(rightChunk);
}
}
else if (CompareUtil.compare(intersection.getEnd(), getEnd()) < 0)
{
rightChunk = new Range<>();
rightChunk.setEnd(getEnd());
if (endValueIsExclusive())
{
rightChunk.setStart(intersection.getEnd());
}
else
{
if (intersection.getStart() instanceof Long)
{
rightChunk.setStart((T) new Long(intersection.getEnd().longValue() + 1));
}
else if (intersection.getStart() instanceof Integer)
{
rightChunk.setStart((T) new Integer(intersection.getEnd().intValue() + 1));
}
}
resultRanges.add(rightChunk);
}
}
else
{
// There was no intersection, hence return the orig.
resultRanges.add(this);
}
return resultRanges;
}
//---------------------------------------------------------------------------
public boolean endValueIsExclusive()
{
if (null == mEndValueIsExclusive)
{
// We can't interrogate T directly so we have to look at the values.
T end = getEnd();
if (end != null)
{
mEndValueIsExclusive = (end instanceof Double || end instanceof Float || end instanceof BigDecimal);
}
else
{
T start = getStart();
if (start != null)
{
mEndValueIsExclusive = (start instanceof Double || start instanceof Float || start instanceof BigDecimal);
}
}
}
return mEndValueIsExclusive;
}
//--------------------------------------------------------------------------
public Range intersection(Range inRange2)
{
Range intersection = null;
if (inRange2 != null)
{
if (getStart() != null)
{
if (inRange2.getStart() != null)
{
if (getEnd() != null)
{
if (inRange2.getEnd() != null)
{
if (getStart().doubleValue() <= inRange2.getEnd().doubleValue()
&& getEnd().doubleValue() >= inRange2.getStart().doubleValue())
{
// -----
// -----
intersection = new Range()
.setStart(getStart().doubleValue() < inRange2.getStart().doubleValue() ? inRange2.getStart() : getStart())
.setEnd(getEnd().doubleValue() > inRange2.getEnd().doubleValue() ? inRange2.getEnd() : getEnd());
}
}
else // Range 2 extends infinitely to the right
{
// -----
// ----->
if (inRange2.contains(getStart().doubleValue()))
{
intersection = new Range()
.setStart(getStart().doubleValue() < inRange2.getStart().doubleValue() ? inRange2.getStart() : getStart())
.setEnd(getEnd());
}
}
}
else if (inRange2.getEnd() != null) // Range 1 extends infinitely to the right
{
// ----->
// -----
if (getStart().doubleValue() <= inRange2.getEnd().doubleValue())
{
intersection = new Range()
.setStart(getStart().doubleValue() < inRange2.getStart().doubleValue() ? inRange2.getStart() : getStart())
.setEnd(inRange2.getEnd());
}
}
else
{
// Both ranges extend infinitely to the right
// ----->
// ------->
intersection = new Range()
.setStart(getStart().doubleValue() > inRange2.getStart().doubleValue() ? inRange2.getStart() : getStart())
.setEnd(getStart().doubleValue() > inRange2.getStart().doubleValue() ? getStart() : inRange2.getStart());
}
}
else // Range 2 extends infinitely to the left
{
if (getEnd() != null)
{
if (inRange2.getEnd() != null)
{ // -----
// <-------
if (getStart().doubleValue() <= inRange2.getEnd().doubleValue())
{
intersection = new Range()
.setStart(getStart())
.setEnd(getEnd().doubleValue() > inRange2.getEnd().doubleValue() ? inRange2.getEnd() : getEnd());
}
}
else // Range 2 extends infinitely to the left & right
{ // -----
// <------->
intersection = clone();
}
}
else if (inRange2.getEnd() != null) // Range 1 extends infinitely to the right
{ // ----->
// <---
if (inRange2.contains(getStart().doubleValue()))
{
intersection = new Range()
.setStart(getStart())
.setEnd(inRange2.getEnd());
}
}
else
{
// Both ranges extend infinitely to the right
// ----->
// <------->
intersection = new Range()
.setStart(getStart());
}
}
}
else if (inRange2.getStart() != null)
{
if (getEnd() != null)
{
if (inRange2.getEnd() != null)
{
// <-----
// -------
if (inRange2.getStart().doubleValue() <= getEnd().doubleValue())
{
intersection = new Range()
.setStart(inRange2.getStart())
.setEnd(getEnd().doubleValue() <= inRange2.getEnd().doubleValue() ? getEnd() : inRange2.getEnd());
}
}
else
{
// <-----
// ------->
if (inRange2.getStart().doubleValue() <= getEnd().doubleValue())
{
intersection = new Range()
.setStart(inRange2.getStart())
.setEnd(getEnd());
}
}
}
else
{
if (inRange2.getEnd() != null)
{
// <----->
// ---
intersection = inRange2.clone();
}
else
{
// <----->
// --->
intersection = new Range()
.setStart(inRange2.getStart());
}
}
}
else
{
if (getEnd() != null)
{
if (inRange2.getEnd() != null)
{
// <-----
// <---
intersection = new Range()
.setEnd(getEnd().doubleValue() <= inRange2.getEnd().doubleValue() ? getEnd() : inRange2.getEnd());
}
else
{
// <-----
// <-------->
intersection = clone();
}
}
else
{
if (inRange2.getEnd() != null)
{
// <----->
// <---
intersection = inRange2.clone();
}
else
{
// <----->
// <----->
intersection = clone();
}
}
}
}
return intersection;
}
//--------------------------------------------------------------------------
public boolean intersects(Range inRange2)
{
boolean result = false;
if (getStart() != null)
{
if (inRange2.getStart() != null)
{
if (getEnd() != null)
{
if (inRange2.getEnd() != null)
{
if (endValueIsExclusive())
{
result = getStart().doubleValue() < inRange2.getEnd().doubleValue()
&& getEnd().doubleValue() > inRange2.getStart().doubleValue();
}
else
{
result = getStart().doubleValue() <= inRange2.getEnd().doubleValue()
&& getEnd().doubleValue() >= inRange2.getStart().doubleValue();
}
}
else // Range 2 extends infinitely to the right
{
result = inRange2.getStart().doubleValue() <= getEnd().doubleValue();
}
}
else if (inRange2.getEnd() != null)
{ // Range 1 extends infinitely to the right
result = inRange2.getEnd().doubleValue() >= getStart().doubleValue();
}
else
{
// Both ranges extend infinitely to the right
result = true;
}
}
else // Range 2 extends infinitely to the left
{
if (inRange2.getEnd() != null)
{
if (endValueIsExclusive())
{
result = getStart().doubleValue() < inRange2.getEnd().doubleValue();
}
else
{
result = getStart().doubleValue() <= inRange2.getEnd().doubleValue();
}
}
else
{
// Range2 is infinite
result = true;
}
}
}
else if (inRange2.getStart() != null)
{
if (getEnd() != null)
{
if (endValueIsExclusive())
{
result = inRange2.getStart().doubleValue() < getEnd().doubleValue();
}
else
{
result = inRange2.getStart().doubleValue() <= getEnd().doubleValue();
}
}
else
{
result = true;
}
}
else
{
// Both ranges extend infinitely to the left
result = true;
}
return result;
}
//--------------------------------------------------------------------------
public void swapStartEndValues()
{
T tmp = getEnd();
setEnd(getStart());
setStart(tmp);
}
}