
ucar.ma2.Range Maven / Gradle / Ivy
/*
* Copyright 1998-2014 University Corporation for Atmospheric Research/Unidata
*
* Portions of this software were developed by the Unidata Program at the
* University Corporation for Atmospheric Research.
*
* Access and use of this software shall impose the following obligations
* and understandings on the user. The user is granted the right, without
* any fee or cost, to use, copy, modify, alter, enhance and distribute
* this software, and any derivative works thereof, and its supporting
* documentation for any purpose whatsoever, provided that this entire
* notice appears in all copies of the software, derivative works and
* supporting documentation. Further, UCAR requests that the user credit
* UCAR/Unidata in any publications that result from the use of this
* software or in any product that includes this software. The names UCAR
* and/or Unidata, however, may not be used in any advertising or publicity
* to endorse or promote any products or commercial entity unless specific
* written permission is obtained from UCAR/Unidata. The user also
* understands that UCAR/Unidata is not obligated to provide the user with
* any support, consulting, training or assistance of any kind with regard
* to the use, operation and performance of this software nor to provide
* the user with any updates, revisions, new versions or "bug fixes."
*
* THIS SOFTWARE IS PROVIDED BY UCAR/UNIDATA "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL UCAR/UNIDATA BE LIABLE FOR ANY SPECIAL,
* INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
* FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
* NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
* WITH THE ACCESS, USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package ucar.ma2;
import net.jcip.annotations.Immutable;
import java.util.List;
import java.util.ArrayList;
import java.util.StringTokenizer;
/**
* Represents a set of integers, used as an index for arrays.
* It should be considered as a subset of the interval of integers [0, length-1] inclusive.
* For example Range(1:11:3) represents the set of integers {1,4,7,10}
* Immutable.
*
* Ranges are monotonically increasing.
* Elements must be nonnegative.
* EMPTY is the empty Range.
* VLEN is for variable length dimensions.
* Note last is inclusive, so standard iteration is
*
* for (int i=range.first(); i<=range.last(); i+= range.stride()) {
* ...
* }
* or use:
* Range.Iterator iter = timeRange.getIterator();
* while (iter.hasNext()) {
* int index = iter.next();
* ...
* }
*
*
* @author caron
*/
@Immutable
public final class Range {
public static final Range EMPTY = new Range();
public static final Range ONE = new Range(1);
public static final Range VLEN = new Range(-1);
private final int n; // number of elements
private final int first; // first value in range
private final int stride; // stride, must be >= 1
private final String name; // optional name
/**
* Used for EMPTY
*/
private Range() {
this.n = 0;
this.first = 0;
this.stride = 1;
this.name = null;
}
/**
* 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);
}
/**
* Create a range starting at zero, with unit stride.
* @param length number of elements in the Rnage
*/
public Range(int length) {
this.name = null;
this.first = 0;
this.stride = 1;
this.n = length;
assert (this.n != 0);
}
/**
* 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 stride.
*
* @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 stride.
*
* @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.n = Math.max(1 + (last - first) / stride, 1);
assert this.n != 0;
}
/**
* Copy Constructor
*
* @param r copy from here
*/
public Range(Range r) {
first = r.first();
n = r.length();
stride = r.stride();
name = r.getName();
assert this.n != 0;
}
/**
* Copy Constructor with name
*
* @param name result name
* @param r copy from here
*/
public Range(String name, Range r) {
this.name = name;
first = r.first();
n = r.length();
stride = r.stride();
assert this.n != 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 = 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();
int last = first + length() - 1;
return new Range(name, first, last, 1);
}
/**
* Create a new Range shifting this range by a constant factor.
*
* @param origin subtract this from first, last
* @return shiften 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 stride = stride();
int last = last() - origin;
return new Range(name, first, last, stride);
}
/**
* 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 ((length() == 0) || (r.length() == 0))
return EMPTY;
if (this == VLEN || r == VLEN)
return VLEN;
int last = Math.min(this.last(), r.last());
int stride = stride() * r.stride();
int useFirst;
if (stride == 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()) / stride;
useFirst = r.first() + incr * stride;
if (useFirst < first()) useFirst += stride;
}
} else if (r.stride() == 1) { // then this has a stride
if (first() >= r.first())
useFirst = first();
else {
int incr = (r.first() - first()) / stride;
useFirst = first() + incr * stride;
if (useFirst < r.first()) useFirst += stride;
}
} else {
throw new UnsupportedOperationException("Intersection when both ranges have a stride");
}
if (useFirst > last)
return EMPTY;
return new Range(name, useFirst, last, stride);
}
/**
* 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 ((length() == 0) || (r.length() == 0))
return false;
if (this == VLEN || r == VLEN)
return true;
int last = Math.min(this.last(), r.last());
int stride = stride() * r.stride();
int useFirst;
if (stride == 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()) / stride;
useFirst = r.first() + incr * stride;
if (useFirst < first()) useFirst += stride;
}
} else if (r.stride() == 1) { // then this has a stride
if (first() >= r.first())
useFirst = first();
else {
int incr = (r.first() - first()) / stride;
useFirst = first() + incr * stride;
if (useFirst < r.first()) useFirst += stride;
}
} 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 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);
}
/**
* Get the number of elements in the range.
* @return the number of elements in the range.
*/
public int length() {
return n;
}
/**
* 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 >= n)
throw new InvalidRangeException("i must be < length");
return first + i * stride;
}
// inverse of element
/**
* Get the index for this element: inverse of element
* @param elem the element of the range
* @return index
* @throws InvalidRangeException if illegal elem
*/
public int index(int elem) throws InvalidRangeException {
if (elem < first)
throw new InvalidRangeException("elem must be >= first");
int result = (elem - first) / stride;
if (result > n)
throw new InvalidRangeException("elem must be <= first = n * stride");
return result;
}
/**
* Is the ith element contained in this Range?
*
* @param i index in the original Range
* @return true if the ith element would be returned by the Range iterator
*/
public boolean contains(int i) {
if (i < first())
return false;
if (i > last())
return false;
if (stride == 1) return true;
return (i - first) % stride == 0;
}
/**
* Get ith element; skip checking, for speed.
*
* @param i index of the element
* @return the i-th element of a range, no check
*/
private int elementNC(int i) {
return first + i * stride;
}
/**
* @return first in range
*/
public int first() {
return first;
}
/**
* @return last in range, inclusive
*/
public int last() {
return first + (n - 1) * stride;
}
/**
* @return stride, must be >= 1
*/
public int stride() {
return stride;
}
/**
* Get name
*
* @return name, or null if none
*/
public String getName() {
return name;
}
/**
* Iterate over Range index
* Usage:
* Iterator iter = range.getIterator();
* while (iter.hasNext()) {
* int index = iter.next();
* doSomething(index);
* }
*
*
* @return Iterator over element indices
*/
public Iterator getIterator() {
return new Iterator();
}
public class Iterator {
private int current = 0;
public boolean hasNext() {
return current < n;
}
public int next() {
return elementNC(current++);
}
}
/**
* 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 = first + i * stride for some integer i.
*
*
* @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.n == 0)
return "EMPTY";
else if(this.n < 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 ((n == 0) && (or.n == 0)) // empty ranges are equal
return true;
return (or.first == first) && (or.n == n) && (or.stride == stride);
}
/**
* Override Object.hashCode() to implement equals.
*/
public int hashCode() {
int result = first();
result = 37 * result + last();
result = 37 * result + stride();
return result;
}
//////////////////////////////////////////////////////////////////////////
// deprecated
/**
* @return Minimum index, inclusive.
* @deprecated use first()
*/
public int min() {
if (n > 0) {
if (stride > 0)
return first;
else
return first + (n - 1) * stride;
} else {
return first;
}
}
/**
* @return Maximum index, inclusive.
* @deprecated use last()
*/
public int max() {
if (n > 0) {
if (stride > 0)
return first + (n - 1) * stride;
else
return first;
} else {
if (stride > 0)
return first - 1;
else
return first + 1;
}
}
///////////////////////////////////////////////////////////////////////////
// deprecated - use Section
/**
* Convert shape array to List of Ranges. Assume 0 origin for all.
*
* @deprecated use Section(int[] shape)
*/
public static List factory(int[] shape) {
ArrayList result = new ArrayList();
for (int i = 0; i < shape.length; i++) {
try {
result.add(new Range(0, Math.max(shape[i] - 1, -1)));
} catch (InvalidRangeException e) {
return null;
}
}
return result;
}
/**
* Check rangeList has no nulls, set from shape array.
*
* @deprecated use Section.setDefaults(int[] shape)
*/
public static List setDefaults(List rangeList, int[] shape) {
try {
// entire rangeList is null
if (rangeList == null) {
rangeList = new ArrayList();
for (int i = 0; i < shape.length; i++) {
rangeList.add(new Range(0, shape[i]));
}
return rangeList;
}
// check that any individual range is null
for (int i = 0; i < shape.length; i++) {
Range r = (Range) rangeList.get(i);
if (r == null) {
rangeList.set(i, new Range(0, shape[i] - 1));
}
}
return rangeList;
}
catch (InvalidRangeException ex) {
return null; // could happen if shape[i] is negetive
}
}
/**
* Convert shape, origin array to List of Ranges.
*
* @deprecated use Section(int[] origin, int[] shape)
*/
public static List factory(int[] origin, int[] shape) throws InvalidRangeException {
ArrayList result = new ArrayList();
for (int i = 0; i < shape.length; i++) {
try {
result.add(new Range(origin[i], origin[i] + shape[i] - 1));
} catch (Exception e) {
throw new InvalidRangeException(e.getMessage());
}
}
return result;
}
/**
* Convert List of Ranges to shape array using the range.length.
*
* @deprecated use Section.getShape()
*/
public static int[] getShape(List ranges) {
if (ranges == null) return null;
int[] result = new int[ranges.size()];
for (int i = 0; i < ranges.size(); i++) {
result[i] = ((Range) ranges.get(i)).length();
}
return result;
}
/**
* @deprecated use Section.toString()
*/
public static String toString(List ranges) {
if (ranges == null) return "";
StringBuilder sbuff = new StringBuilder();
for (int i = 0; i < ranges.size(); i++) {
if (i > 0) sbuff.append(",");
sbuff.append(((Range) ranges.get(i)).length());
}
return sbuff.toString();
}
/**
* /** Compute total number of elements represented by the section.
*
* @param section List of Range objects
* @return total number of elements
* @deprecated use Section.computeSize()
*/
static public long computeSize(List section) {
int[] shape = getShape(section);
return Index.computeSize(shape);
}
/**
* Append a new Range(0,size-1) to the list
*
* @param ranges list of Range
* @param size add this Range
* @return same list
* @throws InvalidRangeException if size < 1
* @deprecated use Section.appendRange(int size)
*/
public static List appendShape(List ranges, int size) throws InvalidRangeException {
ranges.add(new Range(0, size - 1));
return ranges;
}
/**
* Convert List of Ranges to origin array using the range.first.
*
* @deprecated use Section.getOrigin()
*/
public static int[] getOrigin(List ranges) {
if (ranges == null) return null;
int[] result = new int[ranges.size()];
for (int i = 0; i < ranges.size(); i++) {
result[i] = ((Range) ranges.get(i)).first();
}
return result;
}
/**
* Convert List of Ranges to array of Ranges. *
*
* @deprecated use Section.getRanges()
*/
public static Range[] toArray(List ranges) {
if (ranges == null) return null;
return (Range[]) ranges.toArray(new Range[ranges.size()]);
}
/**
* Convert array of Ranges to List of Ranges.
*
* @deprecated use Section.getRanges()
*/
public static List toList(Range[] ranges) {
if (ranges == null) return null;
return java.util.Arrays.asList(ranges);
}
/**
* Convert List of Ranges to String Spec.
* Inverse of parseSpec
*
* @deprecated use Section.toString()
*/
public static String makeSectionSpec(List ranges) {
StringBuilder sbuff = new StringBuilder();
for (int i = 0; i < ranges.size(); i++) {
Range r = (Range) ranges.get(i);
if (i > 0) sbuff.append(",");
sbuff.append(r.toString());
}
return sbuff.toString();
}
/**
* Parse an index section String specification, return equivilent list of ucar.ma2.Range objects.
* The sectionSpec string uses fortran90 array section syntax, namely:
*
* sectionSpec := dims
* dims := dim | dim, dims
* dim := ':' | slice | start ':' end | start ':' end ':' stride
* slice := INTEGER
* start := INTEGER
* stride := INTEGER
* end := INTEGER
*
* where nonterminals are in lower case, terminals are in upper case, literals are in single quotes.
*
* Meaning of index selector :
* ':' = all
* slice = hold index to that value
* start:end = all indices from start to end inclusive
* start:end:stride = all indices from start to end inclusive with given stride
*
*
*
* @param sectionSpec the token to parse, eg "(1:20,:,3,10:20:2)", parenthesis optional
* @return return List of ucar.ma2.Range objects corresponding to the index selection. A null
* Range means "all" (i.e.":") indices in that dimension.
* @throws IllegalArgumentException when sectionSpec is misformed
* @deprecated use new Section(String sectionSpec)
*/
public static List parseSpec(String sectionSpec) throws InvalidRangeException {
ArrayList result = new ArrayList();
Range section;
StringTokenizer stoke = new StringTokenizer(sectionSpec, "(),");
while (stoke.hasMoreTokens()) {
String s = stoke.nextToken().trim();
if (s.equals(":"))
section = null; // all
else if (s.indexOf(':') < 0) { // just a number : slice
try {
int index = Integer.parseInt(s);
section = new Range(index, index);
} catch (NumberFormatException e) {
throw new IllegalArgumentException(" illegal selector: " + s + " part of <" + sectionSpec + ">");
}
} else { // gotta be "start : end" or "start : end : stride"
StringTokenizer stoke2 = new StringTokenizer(s, ":");
String s1 = stoke2.nextToken();
String s2 = stoke2.nextToken();
String s3 = stoke2.hasMoreTokens() ? stoke2.nextToken() : null;
try {
int index1 = Integer.parseInt(s1);
int index2 = Integer.parseInt(s2);
int stride = (s3 != null) ? Integer.parseInt(s3) : 1;
section = new Range(index1, index2, stride);
} catch (NumberFormatException e) {
throw new IllegalArgumentException(" illegal selector: " + s + " part of <" + sectionSpec + ">");
}
}
result.add(section);
}
return result;
}
/**
* Check ranges are valid
*
* @param section
* @param shape
* @return error message, or null if all ok
* @deprecated use Section.checkInRange(int shape[])
*/
public static String checkInRange(List section, int shape[]) {
if (section.size() != shape.length)
return "Number of ranges in section must be =" + shape.length;
for (int i = 0; i < section.size(); i++) {
Range r = (Range) section.get(i);
if (r == null) continue;
if (r.last() >= shape[i])
return "Illegal range for dimension " + i + ": requested " + r.last() + " >= max " + shape[i];
}
return null;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy