jaitools.numeric.RangeUtils Maven / Gradle / Ivy
Show all versions of jt-all Show documentation
/*
* Copyright 2010 Michael Bedward
*
* This file is part of jai-tools.
*
* jai-tools 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 3 of the
* License, or (at your option) any later version.
*
* jai-tools 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 jai-tools. If not, see .
*
*/
package jaitools.numeric;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
* Provides static helper methods to transform, sort and merge {@code Range} objects.
*
* @author Michael Bedward
* @author Daniele Romagnoli, GeoSolutions S.A.S.
* @since 1.0
* @version $Id: RangeUtils.java 1504 2011-03-05 10:56:10Z michael.bedward $
*/
public class RangeUtils {
private static class RangeSortComparator implements Comparator> {
private RangeComparator rc;
public RangeSortComparator(RangeComparator rc) {
this.rc = rc;
}
public int compare(Range r1, Range r2) {
RangeComparator.Result result = rc.compare(r1, r2);
switch (result.getAt(RangeComparator.MIN_MIN)) {
case RangeComparator.LT:
return -1;
case RangeComparator.GT:
return 1;
default:
switch (result.getAt(RangeComparator.MAX_MAX)) {
case RangeComparator.LT:
return -1;
case RangeComparator.GT:
return 1;
default:
return 0;
}
}
}
}
/**
* Creates the complement of a {@code Range}. This is equivalent to subtracting the
* input from the infinite interval.
*
* If the input is a finite interval or point, the result will be a list of
* two {@code Ranges}. For example: the complement of [-5, 5) is made up of
* (-Inf, -5) and [5, Inf).
*
* If the input is an interval open at one end, the result list will contain
* a single {@code Range}. For example: the complement of (-Inf, 5) is [5, Inf).
*
* If the input is a point at positive or negative infinity its complement is,
* by convention, (-Inf, Inf).
*
* If the input is the infinite interval (-Inf, Inf) the result list will be
* empty.
*
* @param the value type
* @param range input range
*
* @return a list of 0, 1 or 2 {@code Ranges} which form the complement
*
* @see #createComplement(java.util.Collection)
*/
public static List> createComplement(Range range) {
return subtract(range, new Range(null, false, null, false));
}
/**
* Creates the complement of the given list of {@code Ranges}. This method first
* calls {@linkplain #simplify} on the inputs and then subtracts each of the
* resulting {@code Ranges} from the whole number line.
*
* @param value type
* @param ranges input ranges
*
* @return a list of {@code Ranges} which form the complement (may be empty)
*
* @see #createComplement(jaitools.numeric.Range)
*/
public static List> createComplement(Collection> ranges) {
List> inputs = simplify(ranges);
List> complements = new ArrayList>();
/*
* Start with the whole number line, then subtract each of
* the input ranges from it
*/
complements.add(new Range(null, true, null, true));
for (int i = 0; i < inputs.size(); i++) {
boolean changed = false;
Range rin = inputs.get(i);
for (int j = 0; j < complements.size() && !changed; j++) {
Range rc = complements.get(j);
List> diff = subtract(rin, rc);
final int ndiff = diff.size();
switch (ndiff) {
case 0:
complements.remove(j);
changed = true;
break;
case 1:
if (!diff.get(0).equals(rc)) {
complements.remove(j);
complements.add(diff.get(0));
changed = true;
}
break;
case 2:
complements.remove(j);
complements.addAll(diff);
changed = true;
break;
}
}
}
return complements;
}
/**
* Sorts a collection of ranges into ascending order of min value, then max value.
*
* @param the value type
* @param ranges the ranges to sort
*
* @return sorted ranges as a {@code List}
*/
public static List> sort(Collection> ranges) {
List> inputs = new ArrayList>(ranges);
Collections.sort(inputs, new RangeSortComparator(new RangeComparator()));
return inputs;
}
/**
* Simplifies a collection of ranges by merging those which overlap.
*
* @param value type
* @param ranges input ranges to simplify
*
* @return simplified ranges sorted by min, then max end-points
*/
public static List> simplify(Collection> ranges) {
List> inputs = new ArrayList>(ranges);
RangeComparator comparator = new RangeComparator();
boolean changed;
do {
changed = false;
for (int i = 0; i < inputs.size()-1 && !changed; i++) {
Range r1 = inputs.get(i);
for (int j = i+1; j < inputs.size() && !changed; j++) {
Range r2 = inputs.get(j);
RangeComparator.Result result = comparator.compare(r1, r2);
if (RangeComparator.isIntersection(result)) {
switch (result) {
case EEEE: // r1 and r2 are equal points
case EEGG: // r2 is a point at min of r1
case LEEG: // equal intervals
case LEGG: // r1 contains r2
case LLEE: // r2 is a point at max of r1
case LLEG: // r1 contains r2
case LLGG: // r1 contains r2
inputs.remove(j);
break;
case EGEG: // r1 is a point at max of r2
case LELE: // r1 is a point at min of r2
case LELG: // r1 is contained in r2
case LGEG: // r1 is contained in r2
case LGLG: // r1 is contained in r2
inputs.remove(i);
break;
case EGGG: // r1 extends from max of r2
case LGGG: // r1 starts within and extends beyond r2
inputs.remove(j);
inputs.remove(i);
inputs.add(0, new Range(r2.getMin(), r2.isMinIncluded(), r1.getMax(), r1.isMaxIncluded()));
break;
case LLLE: // r1 extends to min of r2
case LLLG: // r1 extends into r2
inputs.remove(j);
inputs.remove(i);
inputs.add(0, new Range(r1.getMin(), r1.isMinIncluded(), r2.getMax(), r2.isMaxIncluded()));
break;
}
changed = true;
}
}
}
} while (changed);
/*
* Next, look for any pairs of the form [A, B) [B, C] that can be joined as [A, C]
*/
Collections.sort(inputs, new RangeSortComparator(comparator));
do {
changed = false;
for (int i = 0; i < inputs.size() - 1 && !changed; i++) {
Range r1 = inputs.get(i);
if (r1.isMaxClosed()) {
for (int j = i + 1; j < inputs.size() && !changed; j++) {
Range r2 = inputs.get(j);
if (r2.isMinClosed()) {
if (r1.getMax().compareTo(r2.getMin()) == 0) {
inputs.remove(j);
inputs.remove(i);
inputs.add(i, new Range(r1.getMin(), r1.isMinIncluded(), r2.getMax(), r2.isMaxIncluded()));
changed = true;
}
}
}
}
}
} while (changed);
return inputs;
}
/**
* Gets the intersection of two ranges.
*
* @param value type
* @param r1 first range
* @param r2 second range
*
* @return the intersection as a new range; or {@code null} if there was no intersection
*/
public static Range intersection(Range r1, Range r2) {
if (r1.isPoint()) {
if (r2.isPoint()) {
if (r1.equals(r2)) {
return new Range(r1);
} else {
return null;
}
} else { // r2 is an interval
if ((r1.isMinInf() && r2.isMaxOpen()) ||
(r1.isMinNegInf() && r2.isMinOpen()) ||
r2.contains(r1.getMin())) {
return new Range(r1);
} else {
return null;
}
}
} else if (r2.isPoint()) { // r1 is an interval
if ((r2.isMinInf() && r1.isMaxOpen()) ||
(r2.isMinNegInf() && r1.isMinOpen()) ||
r1.contains(r2.getMin())) {
return new Range(r2);
} else {
return null;
}
}
/*
* From here, we are comparing two interval ranges
*/
RangeComparator rc = new RangeComparator();
RangeComparator.Result result = rc.compare(r1, r2);
if (RangeComparator.isIntersection(result)) {
T min;
boolean minIncluded;
switch (result.getAt(RangeComparator.MIN_MIN)) {
case RangeComparator.LT:
min = r2.getMin();
minIncluded = r2.isMinIncluded();
break;
case RangeComparator.GT:
min = r1.getMin();
minIncluded = r1.isMinIncluded();
break;
default:
min = r1.getMin();
minIncluded = r1.isMinIncluded() || r2.isMinIncluded();
break;
}
T max;
boolean maxIncluded;
switch (result.getAt(RangeComparator.MAX_MAX)) {
case RangeComparator.LT:
max = r1.getMax();
maxIncluded = r1.isMaxIncluded();
break;
case RangeComparator.GT:
max = r2.getMax();
maxIncluded = r2.isMaxIncluded();
break;
default:
max = r1.getMax();
maxIncluded = r1.isMaxIncluded() || r2.isMaxIncluded();
break;
}
return new Range(min, minIncluded, max, maxIncluded);
}
return null;
}
/**
* Subtracts the first range from the second. If the two inputs do not intersect
* the result will be equal to the second. If the two inputs are equal, or the first
* input encloses the second, the result will be an empty list. If the first input
* is strictly contained within the second the result will be two ranges.
*
* @param value type
* @param r1 the first range
* @param r2 the second range
*
* @return 0, 1 or 2 ranges representing the result of {@code r2 - r1}
*/
public static List> subtract(Range r1, Range r2) {
List> difference = new ArrayList>();
/*
* Check for equality between inputs
*/
if (r1.equals(r2)) {
return difference; // empty list
}
Range common = intersection(r1, r2);
/*
* Check for no overlap between inputs
*/
if (common == null) {
difference.add( new Range(r2) );
return difference;
}
/*
* Check if r1 enclosed r2
*/
if (common.equals(r2)) {
return difference; // empty list
}
RangeComparator rc = new RangeComparator();
RangeComparator.Result result = rc.compare(common, r2);
int minComp = result.getAt(RangeComparator.MIN_MIN);
int maxComp = result.getAt(RangeComparator.MAX_MAX);
if (minComp == RangeComparator.EQ) {
difference.add(new Range(common.getMax(), !common.isMaxIncluded(), r2.getMax(), r2.isMaxIncluded()));
} else { // minComp == GT
if (maxComp == RangeComparator.EQ) {
difference.add(new Range(r2.getMin(), r2.isMinIncluded(), common.getMin(), !common.isMinIncluded()));
} else {
// common lies within r2
difference.add(new Range(r2.getMin(), r2.isMinIncluded(), common.getMin(), !common.isMinIncluded()));
difference.add(new Range(common.getMax(), !common.isMaxIncluded(), r2.getMax(), r2.isMaxIncluded()));
}
}
return difference;
}
}