ucar.ma2.Range Maven / Gradle / Ivy
The newest version!
/*
* Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
* See LICENSE for license information.
*/
package ucar.ma2;
import javax.annotation.concurrent.Immutable;
import java.util.Iterator;
/**
* Represents a set of integers, used as an index for arrays.
* No duplicates are allowed.
* It should be considered as a subset of the interval of integers [first(), last()] inclusive.
* For example Range(1:11:3) represents the set of integers {1,4,7,10}
* Note that Range no longer is always strided or monotonic.
* Immutable.
*
* Elements must be nonnegative and unique.
* EMPTY is the empty Range.
* VLEN is for variable length dimensions.
*
Standard iteration is
*
* for (int idx : range) {
* ...
* }
*
*
* @author caron
*/
@Immutable
public class Range implements RangeIterator {
public static final Range EMPTY = new Range(); // used for unlimited dimension = 0
public static final Range ONE = new Range(1);
public static final Range VLEN = new Range(-1);
public static Range make(String name, int len) {
try {
return new Range(name, 0, len - 1, 1);
} catch (InvalidRangeException e) {
throw new RuntimeException(e); // cant happen if len > 0
}
}
////////////////////////////////////////////////////////
protected final int length; // number of elements
private final int first; // first value in range
private final int last; // last value in range, inclusive
private final int stride; // stride, must be >= 1
protected final String name; // optional name
private final boolean useLong;
private final long firstLong; // first value in range
private final long lastLong; // last value in range, inclusive
/**
* Used for EMPTY
*/
private Range() {
this.length = 0;
this.first = 0;
this.last = 0;
this.stride = 1;
this.name = null;
this.useLong = false;
this.firstLong = first;
this.lastLong = last;
}
/**
* Create a range with unit stride.
*
* @param first first value in range
* @param last last value in range, inclusive
* @throws InvalidRangeException elements must be nonnegative, 0 <= first <= last
*/
public Range(int first, int last) throws InvalidRangeException {
this(null, first, last, 1);
}
public Range(long first, long last) throws InvalidRangeException {
this(null, first, last, 1);
}
/**
* Create a range starting at zero, with unit stride.
*
* @param length number of elements in the Range
*/
public Range(int length) {
assert (length != 0);
this.name = null;
this.first = 0;
this.last = length - 1;
this.stride = 1;
this.length = length;
this.useLong = false;
this.firstLong = first;
this.lastLong = last;
}
/**
* Create a named range with unit stride.
*
* @param name name of Range
* @param first first value in range
* @param last last value in range, inclusive
* @throws InvalidRangeException elements must be nonnegative, 0 <= first <= last
*/
public Range(String name, int first, int last) throws InvalidRangeException {
this(name, first, last, 1);
}
/**
* Create a range with a specified values.
*
* @param first first value in range
* @param last last value in range, inclusive
* @param stride stride between consecutive elements, must be > 0
* @throws InvalidRangeException elements must be nonnegative: 0 <= first <= last, stride > 0
*/
public Range(int first, int last, int stride) throws InvalidRangeException {
this(null, first, last, stride);
}
/**
* Create a named range with a specified name and values.
*
* @param name name of Range
* @param first first value in range
* @param last last value in range, inclusive
* @param stride stride between consecutive elements, must be > 0
* @throws InvalidRangeException elements must be nonnegative: 0 <= first <= last, stride > 0
*/
public Range(String name, int first, int last, int stride) throws InvalidRangeException {
if (first < 0)
throw new InvalidRangeException("first (" + first + ") must be >= 0");
if (last < first)
throw new InvalidRangeException("last (" + last + ") must be >= first (" + first + ")");
if (stride < 1)
throw new InvalidRangeException("stride (" + stride + ") must be > 0");
this.name = name;
this.first = first;
this.stride = stride;
this.length = 1 + (last - first) / stride;
this.last = first + (this.length - 1) * stride;
if (stride == 1)
assert this.last == last;
assert this.length != 0;
this.useLong = false;
this.firstLong = first;
this.lastLong = last;
}
/**
* Create a named range with a specified name and long values.
*
* @param name name of Range
* @param first first value in range
* @param last last value in range, inclusive
* @param stride stride between consecutive elements, must be > 0
* @throws InvalidRangeException elements must be nonnegative: 0 <= first <= last, stride > 0
*/
public Range(String name, long first, long last, int stride) throws InvalidRangeException {
if (first < 0)
throw new InvalidRangeException("first (" + first + ") must be >= 0");
if (last < first)
throw new InvalidRangeException("last (" + last + ") must be >= first (" + first + ")");
if (stride < 1)
throw new InvalidRangeException("stride (" + stride + ") must be > 0");
this.name = name;
this.first = (int) first;
this.stride = stride;
this.length = 1 + ((int) (last - first)) / stride;
this.last = this.first + (this.length - 1) * stride;
if (stride == 1)
assert this.last == last;
assert this.length != 0;
this.useLong = true;
this.firstLong = first;
this.lastLong = last;
}
protected Range(String name, int first, int last, int stride, int length) throws InvalidRangeException {
if (first < 0)
throw new InvalidRangeException("first (" + first + ") must be >= 0");
if (last < first)
throw new InvalidRangeException("last (" + last + ") must be >= first (" + first + ")");
if (stride < 1)
throw new InvalidRangeException("stride (" + stride + ") must be > 0");
if (length < (1 + last - first) / stride)
throw new InvalidRangeException("length (" + length + ") must be > (1 + last - first) / stride");
this.name = name;
this.first = first;
this.last = last;
this.stride = stride;
this.length = length;
this.useLong = false;
this.firstLong = first;
this.lastLong = last;
}
// copy on change
public Range setStride(int stride) throws InvalidRangeException {
return new Range(this.first(), this.last(), stride);
}
@Override
public Range setName(String name) {
if (name.equals(this.getName())) return this;
try {
return new Range(name, first, last, stride, length);
} catch (InvalidRangeException e) {
throw new RuntimeException(e); // cant happen
}
}
/**
* @return name, or null if none
*/
public String getName() {
return name;
}
/**
* @return first in range
*/
public int first() {
return first;
}
/**
* @return last in range, inclusive
*/
public int last() {
return last;
}
/**
* @return the number of elements in the range.
*/
public int length() {
return length;
}
/**
* @return stride, must be >= 1 when evenly strided, -1 if not
* // * @deprecated use iterator(), dont assume evenly strided
*/
public int stride() {
return stride;
}
public long firstLong() {
return firstLong;
}
public long lastLong() {
return lastLong;
}
/////////////////////////////////////////////
/**
* Is want contained in this Range?
*
* @param want index in the original Range
* @return true if the ith element would be returned by the Range iterator
*/
public boolean contains(int want) {
if (want < first())
return false;
if (want > last())
return false;
if (stride == 1) return true;
return (want - first) % stride == 0;
}
/**
* Create a new Range by composing a Range that is reletive to this Range.
* Revised 2013/04/19 by Dennis Heimbigner to handle edge cases.
* See the commentary associated with the netcdf-c file dceconstraints.h,
* function dceslicecompose().
*
* @param r range reletive to base
* @return combined Range, may be EMPTY
* @throws InvalidRangeException elements must be nonnegative, 0 <= first <= last
*/
public Range compose(Range r) throws InvalidRangeException {
if ((length() == 0) || (r.length() == 0))
return EMPTY;
if (this == VLEN || r == VLEN)
return VLEN;
/* if(false) {// Original version
// Note that this version assumes that range r is
// correct with respect to this.
int first = element(r.first());
int stride = stride() * r.stride();
int last = element(r.last());
return new Range(name, first, last, stride);
} else {//new version: handles versions all values of r. */
int sr_stride = this.stride * r.stride;
int sr_first = element(r.first()); // MAP(this,i) == element(i)
int lastx = element(r.last());
int sr_last = (last() < lastx ? last() : lastx); //min(last(),lastx)
//unused int sr_length = (sr_last + 1) - sr_first;
return new Range(name, sr_first, sr_last, sr_stride);
}
/**
* Create a new Range by compacting this Range by removing the stride.
* first = first/stride, last=last/stride, stride=1.
*
* @return compacted Range
* @throws InvalidRangeException elements must be nonnegative, 0 <= first <= last
*/
public Range compact() throws InvalidRangeException {
if (stride == 1) return this;
int first = first() / stride; // LOOK WTF ?
int last = first + length() - 1;
return new Range(name, first, last, 1);
}
/**
* Get ith element
*
* @param i index of the element
* @return the i-th element of a range.
* @throws InvalidRangeException i must be: 0 <= i < length
*/
public int element(int i) throws InvalidRangeException {
if (i < 0)
throw new InvalidRangeException("i must be >= 0");
if (i >= length)
throw new InvalidRangeException("i must be < length");
return first + i * stride;
}
/**
* Get the index for this element: inverse of element
*
* @param want the element of the range
* @return index
* @throws InvalidRangeException if illegal elem
*/
public int index(int want) throws InvalidRangeException {
if (want < first)
throw new InvalidRangeException("elem must be >= first");
int result = (want - first) / stride;
if (result > length)
throw new InvalidRangeException("elem must be <= first = n * stride");
return result;
}
public long indexLong(long want) throws InvalidRangeException {
if (want < firstLong)
throw new InvalidRangeException("elem must be >= first");
long result = (want - firstLong) / stride;
if (result > length)
throw new InvalidRangeException("elem must be <= first = n * stride");
return result;
}
/**
* Create a new Range by intersecting with a Range using same interval as this Range.
* NOTE: we dont yet support intersection when both Ranges have strides
*
* @param r range to intersect
* @return intersected Range, may be EMPTY
* @throws InvalidRangeException elements must be nonnegative
*/
public Range intersect(Range r) throws InvalidRangeException {
if (useLong) {
return intersectLong(r);
}
if ((length() == 0) || (r.length() == 0))
return EMPTY;
if (this == VLEN || r == VLEN)
return VLEN;
int last = Math.min(this.last(), r.last());
int resultStride = stride * r.stride();
int useFirst;
if (resultStride == 1) { // both strides are 1
useFirst = Math.max(this.first(), r.first());
} else if (stride == 1) { // then r has a stride
if (r.first() >= first())
useFirst = r.first();
else {
int incr = (first() - r.first()) / resultStride;
useFirst = r.first() + incr * resultStride;
if (useFirst < first()) useFirst += resultStride;
}
} else if (r.stride == 1) { // then this has a stride
if (first() >= r.first())
useFirst = first();
else {
int incr = (r.first() - first()) / resultStride;
useFirst = first() + incr * resultStride;
if (useFirst < r.first()) useFirst += resultStride;
}
} else {
throw new UnsupportedOperationException("Intersection when both ranges have a stride");
}
if (useFirst > last)
return EMPTY;
return new Range(name, useFirst, last, resultStride);
}
Range intersectLong(Range r) throws InvalidRangeException {
if ((length() == 0) || (r.length() == 0))
return EMPTY;
if (this == VLEN || r == VLEN)
return VLEN;
long last = Math.min(this.lastLong(), r.lastLong());
int resultStride = stride * r.stride();
long useFirst;
if (resultStride == 1) { // both strides are 1
useFirst = Math.max(this.firstLong(), r.firstLong());
} else if (stride == 1) { // then r has a stride
if (r.firstLong() >= firstLong())
useFirst = r.firstLong();
else {
long incr = (firstLong() - r.firstLong()) / resultStride;
useFirst = r.firstLong() + incr * resultStride;
if (useFirst < firstLong()) useFirst += resultStride;
}
} else if (r.stride == 1) { // then this has a stride
if (firstLong() >= r.firstLong())
useFirst = firstLong();
else {
long incr = (r.firstLong() - firstLong()) / resultStride;
useFirst = firstLong() + incr * resultStride;
if (useFirst < r.firstLong()) useFirst += resultStride;
}
} else {
throw new UnsupportedOperationException("Intersection when both ranges have a stride");
}
if (useFirst > last)
return EMPTY;
return new Range(name, useFirst, last, resultStride);
}
/**
* Determine if a given Range intersects this one.
* NOTE: we dont yet support intersection when both Ranges have strides
*
* @param r range to intersect
* @return true if they intersect
* @throws UnsupportedOperationException if both Ranges have strides
*/
public boolean intersects(Range r) {
if (useLong) {
return intersectsLong(r);
}
if ((length() == 0) || (r.length() == 0))
return false;
if (this == VLEN || r == VLEN)
return true;
int last = Math.min(this.last(), r.last());
int resultStride = stride * r.stride();
int useFirst;
if (resultStride == 1) { // both strides are 1
useFirst = Math.max(this.first(), r.first());
} else if (stride == 1) { // then r has a stride
if (r.first() >= first())
useFirst = r.first();
else {
int incr = (first() - r.first()) / resultStride;
useFirst = r.first() + incr * resultStride;
if (useFirst < first()) useFirst += resultStride;
}
} else if (r.stride() == 1) { // then this has a stride
if (first() >= r.first())
useFirst = first();
else {
int incr = (r.first() - first()) / resultStride;
useFirst = first() + incr * resultStride;
if (useFirst < r.first()) useFirst += resultStride;
}
} else {
throw new UnsupportedOperationException("Intersection when both ranges have a stride");
}
return (useFirst <= last);
}
boolean intersectsLong(Range r) {
if ((length() == 0) || (r.length() == 0))
return false;
if (this == VLEN || r == VLEN)
return true;
long last = Math.min(this.lastLong(), r.lastLong());
int resultStride = stride * r.stride();
long useFirst;
if (resultStride == 1) { // both strides are 1
useFirst = Math.max(this.firstLong(), r.firstLong());
} else if (stride == 1) { // then r has a stride
if (r.firstLong() >= firstLong())
useFirst = r.firstLong();
else {
long incr = (firstLong() - r.firstLong()) / resultStride;
useFirst = r.firstLong() + incr * resultStride;
if (useFirst < firstLong()) useFirst += resultStride;
}
} else if (r.stride() == 1) { // then this has a stride
if (firstLong() >= r.firstLong())
useFirst = firstLong();
else {
long incr = (r.firstLong() - firstLong()) / resultStride;
useFirst = firstLong() + incr * resultStride;
if (useFirst < r.firstLong()) useFirst += resultStride;
}
} else {
throw new UnsupportedOperationException("Intersection when both ranges have a stride");
}
return (useFirst <= last);
}
/**
* If this range is completely past the wanted range
*
* @param want desired range
* @return true if first() > want.last()
*/
public boolean past(Range want) {
return (first() > want.last());
}
/**
* Create a new Range shifting this range by a constant factor.
*
* @param origin subtract this from each element
* @return shifted range
* @throws InvalidRangeException elements must be nonnegative, 0 <= first <= last
*/
public Range shiftOrigin(int origin) throws InvalidRangeException {
if (this == VLEN)
return VLEN;
int first = first() - origin;
int last = last() - origin;
return new Range(name, first, last, stride);
}
/**
* Create a new Range by making the union with a Range using same interval as this Range.
* NOTE: no strides
*
* @param r range to add
* @return intersected Range, may be EMPTY
* @throws InvalidRangeException elements must be nonnegative
*/
public Range union(Range r) throws InvalidRangeException {
if (length() == 0)
return r;
if (this == VLEN || r == VLEN)
return VLEN;
if (r.length() == 0)
return this;
int first = Math.min(this.first(), r.first());
int last = Math.max(this.last(), r.last());
return new Range(name, first, last);
}
/**
* Find the first element in a strided array after some index start.
* Return the smallest element k in the Range, such that
* - k >= first
*
- k >= start
*
- k <= last
*
- k = element of this Range
*
*
* @param start starting index
* @return first in interval, else -1 if there is no such element.
*/
public int getFirstInInterval(int start) {
if (start > last()) return -1;
if (start <= first) return first;
if (stride == 1) return start;
int offset = start - first;
int i = offset / stride;
i = (offset % stride == 0) ? i : i + 1; // round up
return first + i * stride;
}
public String toString() {
if (this.length == 0)
return ":"; // EMPTY
else if (this.length < 0)
return ":"; // VLEN
else
return first + ":" + last() + (stride > 1 ? ":" + stride : "");
}
/**
* Range elements with same first, last, stride are equal.
*/
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Range)) return false; // this catches nulls
Range or = (Range) o;
if ((length == 0) && (or.length == 0)) // empty ranges are equal
return true;
return (or.first == first) && (or.length == length) && (or.stride == stride) && (or.last == last);
}
/**
* Override Object.hashCode() to implement equals.
*/
public int hashCode() {
int result = first;
result = 37 * result + last;
result = 37 * result + stride;
result = 37 * result + length;
return result;
}
/////////////////////////////////////////////////////////
/**
* Iterate over Range index
*
* @return Iterator over element indices
* @deprecated use iterator() or foreach
*/
public Iterator getIterator() {
return new MyIterator();
}
@Override
public Iterator iterator() {
return new MyIterator();
}
private class MyIterator implements java.util.Iterator {
private int current = 0;
public boolean hasNext() {
return current < length;
}
public Integer next() {
return elementNC(current++);
}
}
/**
* Get ith element; skip checking, for speed.
*/
private int elementNC(int i) {
return first + i * stride;
}
}