/*
* Copyright 2013 Gordon Burgett and individual contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.xflatdb.xflat.query;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
/**
* Represents an immutable set of values defined in terms of several intervals.
* The set is the union of all the intervals.
* @author gordon
*/
public class IntervalSet<T> {
private final List<Interval<T>> intervals;
/**
* Gets all the intervals in this IntervalSet in ascending order.
*/
public List<Interval<T>> getIntervals(){
return intervals;
}
private IntervalSet(Interval<T>... intervals){
this.intervals = Arrays.asList(intervals);
}
private IntervalSet(List<Interval<T>> intervals){
this.intervals = Collections.unmodifiableList(intervals);
}
/**
* Creates an interval set representing all values less than the given value,
* to negative infinity.
* That is, the interval (-∞, value)
* @param <U>
* @param value The exclusive end value of the interval to create.
* @return An interval set containing one interval
*/
public static <U> IntervalSet<U> lt(U value){
Interval<U> i = new Interval<>((U)null, false, value, false);
return new IntervalSet<>(i);
}
/**
* Creates an interval set representing all values less than or equal to the given value,
* to negative infinity.
* That is, the interval (-∞, value]
* @param <U>
* @param value The inclusive end value of the interval to create.
* @return An interval set containing one interval
*/
public static <U> IntervalSet<U> lte(U value){
Interval i = new Interval<>((U)null, false, value, true);
return new IntervalSet<>(i);
}
/**
* Creates an interval set representing all values greater than the given value,
* to positive infinity.
* That is, the interval (value, ∞)
* @param <U>
* @param value The exclusive begin value of the interval to create.
* @return An interval set containing one interval
*/
public static <U> IntervalSet<U> gt(U value){
Interval i = new Interval<>(value, false, null, false);
return new IntervalSet<>(i);
}
/**
* Creates an interval set representing all values greater than or equal to the given value,
* to positive infinity.
* That is, the interval [value, ∞)
* @param <U>
* @param value The inclusive begin value of the interval to create.
* @return An interval set containing one interval
*/
public static <U> IntervalSet<U> gte(U value){
Interval i = new Interval<>(value, true, null, false);
return new IntervalSet<>(i);
}
/**
* Creates an IntervalSet representing all values between the two given values exclusive.
* That is, the interval (lower, upper)
* <p/>
* This is equivalent to {@link #gt(java.lang.Object) gt(lower)} {@link #intersection(org.xflatdb.xflat.query.IntervalSet, java.util.Comparator) ∩}
* {@link #lt(java.lang.Object) lt(upper)}.
* @param <U>
* @param lower The exclusive lower bound of the interval set.
* @param upper The inclusive upper bound of the interval set.
* @return the interval "(lower, upper)"
*/
public static <U> IntervalSet<U> between(U lower, U upper){
Interval i = new Interval<>(lower, false, upper, false);
return new IntervalSet<>(i);
}
/**
* Creates an interval set containing exactly one value.
* That is, the interval [value, value]
* @param <U>
* @param value The one value that should be in the interval.
* @return an IntervalSet containing one Interval
*/
public static <U> IntervalSet<U> eq(U value){
Interval i = new Interval<>(value, true, value, true);
return new IntervalSet<>(i);
}
/**
* Creates an interval set containing all values except one.
* That is, the interval { (-∞, value) U (value, ∞) }
* @param <U>
* @param value The one value that should be in the interval.
* @return an IntervalSet containing two Intervals
*/
public static <U> IntervalSet<U> ne(U value){
return new IntervalSet<>(
new Interval<>(null, false, value, false),
new Interval<>(value, false, null, false));
}
/**
* Creates an interval set containing the entire number range.
* That is, the interval (-∞, ∞)
* @param <U>
* @return the interval "(-∞, ∞)"
*/
public static <U> IntervalSet<U> all(){
Interval i = new Interval<>(null, false, null, false);
return new IntervalSet<>(i);
}
/**
* Creates an interval set containing no intervals.
* That is, the empty set.
* @param <U>
* @return the empty interval.
*/
public static <U> IntervalSet<U> none(){
Interval i = new Interval<>(null, false, null, false);
return new IntervalSet<>(i);
}
//gets a Comparator that compares based on the beginnings of intervals
private Comparator<Interval<T>> sortingComparer(final Comparator<T> itemComparer){
return new IntervalComparator<>(itemComparer);
}
/**
* Gets the union of this IntervalSet with the other IntervalSet, using the given comparator.
* The returned interval set will contain the minimum number of {@link Interval} objects
* necessary to represent the union of this interval set with the other.
* <p/>
* The returned interval set is a new instance.
* @param other The other interval set with which to union this interval set.
* @param comparer The comparator for begin and end values of the interval set.
* @return A new interval set containing the union.
*/
public IntervalSet<T> union(IntervalSet<T> other, final Comparator<T> comparer){
Comparator<Interval<T>> sorter = sortingComparer(comparer);
List<Interval<T>> allIntervals = new ArrayList<>(this.intervals.size() + other.intervals.size());
allIntervals.addAll(this.intervals);
allIntervals.addAll(other.intervals);
Collections.sort(allIntervals, sorter);
List<Interval<T>> ret = new ArrayList<>();
collapseIntervals(allIntervals, comparer, ret);
return new IntervalSet<>(ret);
}
/**
* Gets the intersection of this interval set with another interval set.
* The returned interval set will contain the minimum number of {@link Interval} objects
* necessary to represent the intersection of this interval set with the other.
* <p/>
* The returned interval set is a new instance.
* @param other The other interval set which has intersecting intervals.
* @param comparer The comparer for values of the interval set.
* @return the intersection of this interval and the given interval.
*/
public IntervalSet<T> intersection(IntervalSet<T> other, final Comparator<T> comparer){
Comparator<Interval<T>> sorter = sortingComparer(comparer);
List<Interval<T>> temp = new ArrayList<>();
getIntersections(this.intervals, other.intervals, sorter, comparer, temp);
List<Interval<T>> ret = new ArrayList<>();
collapseIntervals(temp, comparer, ret);
return new IntervalSet<>(ret);
}
private void getIntersections(Iterable<Interval<T>> myIntervals, Iterable<Interval<T>> theirIntervals, Comparator<Interval<T>> sorter, Comparator<T> comparer, List<Interval<T>> addTo){
Iterator<Interval<T>> mineIterator = myIntervals.iterator();
Iterator<Interval<T>> theirsIterator = theirIntervals.iterator();
if(!mineIterator.hasNext() || !theirsIterator.hasNext()){
//intersection with nothing is nothing
return;
}
Interval<T> mine = mineIterator.next();
Interval<T> theirs = theirsIterator.next();
do{
int compare = sorter.compare(mine, theirs);
Interval<T> first, second;
if(compare <= 0){
first = mine;
second = theirs;
}
else{
//theirs is before mine
first = theirs;
second = mine;
}
int endCompare = Interval.compareEnd(mine, theirs, comparer);
if(intersectOrTouching(first, second, comparer)){
//intersecting interval is begin of second to end of the smaller end value
if(endCompare <= 0){
addTo.add(new Interval<>(second.begin, second.beginInclusive,
mine.end, mine.endInclusive));
}
else{
addTo.add(new Interval<>(second.begin, second.beginInclusive,
theirs.end, theirs.endInclusive));
}
}
//advance the one with the smaller endValue, or both if equal
if(endCompare <= 0){
if(!mineIterator.hasNext())
break;
mine = mineIterator.next();
}
if(endCompare > 0){
if(!theirsIterator.hasNext())
break;
theirs = theirsIterator.next();
}
}while(true);
//no more intersections once one is out
}
private void collapseIntervals(Iterable<Interval<T>> allIntervals, Comparator<T> comparer, List<Interval<T>> addTo){
Iterator<Interval<T>> it = allIntervals.iterator();
if(!it.hasNext()){
//no intervals?
return;
}
Interval<T> last = it.next();
while(it.hasNext()){
Interval<T> current = it.next();
if(intersectOrTouching(last, current, comparer)){
//current's lower is already > last,
//for union we extend last's end to the greater end
int endCompare = Interval.compareEnd(last, current, comparer);
//if last completely envelops current (equal to or greater than, comparing inclusivity)
if(endCompare > 0 || (endCompare == 0 && (last.endInclusive || !current.endInclusive))){
//ignore current
continue;
}
//last must be extended cause it's greater, or it is end inclusive and current is not.
last = new Interval<>(last.begin, last.beginInclusive,
current.end, current.endInclusive);
continue;
}
//does not intersect
addTo.add(last);
last = current;
}
addTo.add(last);
}
private boolean intersectOrTouching(Interval<T> a, Interval<T> b, Comparator<T> comparer){
//we can assume that a's begin is <= b's begin, so we need to see if b's begin is < a's end
if(a.end == null || b.begin == null){
//either a extends to infinity or b starts from infinity past,
//in either case they intersect.
return true;
}
int compare = comparer.compare(a.end, b.begin);
if(compare == 0){
//if either is inclusive then they touch or intersect, in both cases return true
return a.endInclusive || b.beginInclusive;
}
return compare > 0;
}
/**
* Tests whether this IntervalSet intersects another IntervalSet.
* @param other The other interval set to test against
* @param comparer The comparer comparing values.
* @return true if any interval in this interval set intersects any interval in the other.
*/
public boolean intersects(IntervalSet<T> other, Comparator<T> comparer){
// O(n^2), but theres not gonna be very many in any interval set.
// Any other algorithms would be too much overhead.
for(int i = 0; i < this.intervals.size(); i++){
for(int j = 0; j < other.intervals.size(); j++){
if(this.intervals.get(i).intersects(other.intervals.get(j), comparer)){
return true;
}
}
}
return false;
}
/**
* Tests whether this IntervalSet contains any intervals that intersect the given interval.
* @param other The interval to test.
* @param comparer The comparer comparing begin and end values.
* @return true if any interval in this interval set intersects the given interval.
*/
public boolean intersects(Interval<T> other, Comparator<T> comparer){
for(int i = 0; i < this.intervals.size(); i++){
if(this.intervals.get(i).intersects(other, comparer)){
return true;
}
}
return false;
}
@Override
public int hashCode() {
int hash = 5;
int size = this.intervals.size();
hash = 59 * hash + size;
for(int i = 0; i < size; i++){
hash = 59 * hash + Objects.hashCode(this.intervals.get(i));
}
return hash;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final IntervalSet<T> other = (IntervalSet<T>) obj;
int size = this.intervals.size();
if(size != other.intervals.size()){
return false;
}
for(int i = 0; i < size; i++){
if(!this.intervals.get(i).equals(other.intervals.get(i))){
return false;
}
}
return true;
}
private String stringValue = null;
/**
* Returns the string representation of this interval set, which is the
* string representation of each interval in this set concatenated with the
* union operator "U".
* @return The string representation of this interval set.
*/
@Override
public String toString(){
if(stringValue == null){
StringBuilder ret = new StringBuilder();
for(int i = 0; i < intervals.size(); i++){
if( i > 0){
ret.append(" U ");
}
intervals.get(i).toString(ret);
}
stringValue = ret.toString();
}
return stringValue;
}
}