All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.xmlcml.euclid.IntRange Maven / Gradle / Ivy

There is a newer version: 2.9
Show newest version
/**
 *    Copyright 2011 Peter Murray-Rust
 *
 *    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.xmlcml.euclid;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;

/**
 * maximum and minimum values
 * 
 * Contains two ints representing the minimum and maximum of an allowed or
 * observed range.
 * 

* Default is range with low {@literal >} high; this can be regarded as the uninitialised * state. If points are added to a default IntRange it becomes initialised. * * @author (C) P. Murray-Rust, 1996 */ public class IntRange implements EuclidConstants, Comparable { private static final Logger LOG = Logger.getLogger(IntRange.class); static { LOG.setLevel(Level.DEBUG); } private final static Pattern CURLY_PATTERN1 = Pattern.compile("\\{([^,]+)\\}"); private final static Pattern CURLY_PATTERN2 = Pattern.compile("\\{([^,]+),([^,]+)\\}"); private final static String ANY = "*"; public final static IntRangeComparator ASCENDING_MIN_COMPARATOR = new IntRangeComparator(); /** * maximum of range */ protected int maxval; /** * minimum of range */ protected int minval; /** * creates invalid range from MAX_VALUE to MIN_VALUE */ public IntRange() { minval = Integer.MAX_VALUE; maxval = Integer.MIN_VALUE; } /** * initialise with min and max values; if minv {@literal >} maxv create inValid * IntRange * * @param minv * @param maxv */ public IntRange(int minv, int maxv) { maxval = maxv; minval = minv; if (minval > maxval) { minval = Integer.MAX_VALUE; maxval = Integer.MIN_VALUE; } } /** * copy constructor * * @param r */ public IntRange(IntRange r) { minval = r.minval; maxval = r.maxval; } /** * * @param r */ public IntRange(RealRange r) { minval = (int) Math.round(r.minval); maxval = (int) Math.round(r.maxval); } /** * a Range is only valid if its maxval is not less than its minval; this * tests for uninitialised ranges * * @return valid */ public boolean isValid() { return (minval <= maxval); } /** * invalid ranges return false * * @param r * @return equals * */ public boolean isEqualTo(IntRange r) { return (r != null && minval == r.minval && maxval == r.maxval && minval <= maxval); } @Override public boolean equals(Object o) { boolean equals = false; if (o != null && o instanceof IntRange) { IntRange ir =(IntRange) o; equals = this.minval == ir.minval && this.maxval == ir.maxval; } return equals; } @Override public int hashCode() { return 17*minval + 31*maxval; } /** * combine two ranges if both valid; takes greatest limits of both, else * returns InValid * * @param r2 * @return range */ public IntRange plus(IntRange r2) { if (!this.isValid()) { if (r2 == null || !r2.isValid()) { return new IntRange(); } return new IntRange(r2); } IntRange temp = new IntRange(); temp = new IntRange(Math.min(minval, r2.minval), Math.max(maxval, r2.maxval)); return temp; } public boolean intersectsWith(IntRange r2) { IntRange r = this.intersectionWith(r2); return r != null && r.isValid(); } /** * intersect two ranges and take the range common to both; return invalid * range if no overlap * * @param r2 * @return range */ public IntRange intersectionWith(IntRange r2) { if (!isValid() || r2 == null || !r2.isValid()) { return new IntRange(); } int minv = Math.max(minval, r2.minval); int maxv = Math.min(maxval, r2.maxval); return new IntRange(minv, maxv); } /** * get minimum value (MAX_VALUE if inValid) * * @return min */ public int getMin() { return minval; } /** * get maximum value (MIN_VALUE if inValid) * * @return max * */ public int getMax() { return maxval; } /** * get range (MIN_VALUE if invalid) * * @return range */ public int getRange() { if (!isValid()) return Integer.MIN_VALUE; return maxval - minval; } /** * does one range include another * * @param r2 * @return includes */ public boolean includes(IntRange r2) { return (r2 != null && r2.isValid() && this.includes(r2.getMin()) && this .includes(r2.getMax())); } /** * is a int within a IntRange * * @param f * @return includes If inValid, return false */ public boolean includes(int f) { return f >= minval && f <= maxval; } /** * synonym for includes() * * @param f * @return includes */ public boolean contains(int f) { return includes(f); } /** * add a value to a range * * @param x */ public void add(int x) { maxval = Math.max(maxval, x); minval = Math.min(minval, x); } /** * to string * * @return string */ public String toString() { return (minval > maxval) ? "NULL" : S_LBRAK + minval + S_COMMA + maxval + S_RBRAK; } /** comparees on min values * * @param intRange * @return */ public int compareTo(IntRange intRange) { if (intRange == null) { return -1; } else if (this.minval < intRange.minval) { return -1; } else if (this.minval > intRange.minval) { return 1; } else { if (this.maxval < intRange.maxval) { return -1; } else if (this.maxval > intRange.maxval) { return 1; } } return 0; } /** makes new IntRange extended by deltaMin and deltaMax. * * the effect is for positive numbers to increase the range. * if extensions are negative they are applied, but may result * in invalid range (this is not checked at this stage). *

* Does not alter this. *

* * @param minExtend subtracted from min * @param maxExtend added to max */ public IntRange getRangeExtendedBy(int minExtend, int maxExtend) { return new IntRange(minval - minExtend, maxval + maxExtend); } /** do ranges touch but not overlap? * * range from [a,b] inclusive touches [c,a-1] or [b+1,c] * * ([a,b] overlaps with [b,c]) * @param range * @return */ public boolean touches(IntRange range) { return range != null && (this.maxval + 1 == range.minval || range.maxval + 1 == this.minval); } /** mid point of range. * * @return */ public int getMidPoint() { return (minval + maxval)/2; } /** interprets a String as an IntRange. * * {m,n} is interpreted as IntRange(m,n) * {*,n} is interpreted as IntRange(any,n) * {m,*} is interpreted as IntRange(m,any) * {*,*} is interpreted as IntRange(any,any) * {m} is interpreted as IntRange(m,m) * {*} is interpreted as IntRange(any,any) * * @param token * @return null if cannot create a valid IntRange */ public static IntRange parseCurlyBracketString(String token) { IntRange intRange = null; if (token != null) { Integer min = null; Integer max = null; token = token.replaceAll("\\s+", ""); // strip spaces Matcher matcher = CURLY_PATTERN2.matcher(token); try { if (matcher.matches()) { String minS = matcher.group(1); String maxS = matcher.group(2); min = (ANY.equals(minS)) ? -Integer.MAX_VALUE : Integer.parseInt(minS); max = (ANY.equals(maxS)) ? Integer.MAX_VALUE : Integer.parseInt(maxS); } else { matcher = CURLY_PATTERN1.matcher(token); if (matcher.matches()) { String minS = matcher.group(1); min = (ANY.equals(minS)) ? -Integer.MAX_VALUE : Integer.parseInt(minS); max = min; } } intRange = new IntRange(min, max); } catch (Exception e) { LOG.error("Cannot parse range: "+token); } } return intRange; } /** creates a list of IntRanges from {...} syntax. * * uses parseCurlyBracketString() * * @param tokens * @return */ public static List createIntRangeList(List tokens) { List intRangeList = new ArrayList(); for (String token : tokens) { IntRange intRange = IntRange.parseCurlyBracketString(token); if (intRange == null) { throw new RuntimeException("Cannot parse ("+token+") as IntRange in : "+tokens); } intRangeList.add(intRange); } return intRangeList; } /** joins intRanges A and B when A.getMax() == B.getMin(). * Ranges must either "join" within a tolerance or be disjoint. * Overlapping ranges will throw an RuntimeException. * * (1,5), (5,10), (10,15) * creates * (1,15) * * (1,5), (5,10), (15,20), (20, 25) * creates * (1,10) (15, 25) * * (1,5), (15,20) * creates * (1,5) (15, 20) * * (1,5) (2,10) (5, 12) throws an Exception * * @param rangeList can be in any order, not altered * @param tolerance allows for overlap, i.e. (1,5) and (4,10) would "touch" , so would (1,5) and (6,10) * @return List of non-touching joined ranges, sorted by new R.getMin() */ public static List joinRanges(List rangeList, int tolerance) { if (rangeList.size() <= 1) { return rangeList; } List sortedList = new ArrayList(); for (IntRange range : rangeList) { sortedList.add(new IntRange(range)); // must copy to preserve original list unaltered } Collections.sort(sortedList, ASCENDING_MIN_COMPARATOR); List newList = new ArrayList(); IntRange currentRange = sortedList.get(0); newList.add(currentRange); for (int i = 1; i < sortedList.size(); i++) { IntRange range = sortedList.get(i); if (currentRange.canJoin(range, tolerance)) { currentRange.setMax(range.getMax()); } else { if (range.getMin() < currentRange.getMax()) { throw new RuntimeException("ranges overlap: "+currentRange+" / "+range); } currentRange = range; newList.add(currentRange); } } return newList; } /** can two ranges be joined at a common point? * (1,5) and (5,10) can be joined. * Because ranges are sometimes created from real numbers, a tolerance is allowed. * This should should normally not be set beyond 1 * (1,5) and (6,10) would join with tolerance=1, as would (1,5) and (4,10) * (1,5) and (3,10) would not be joinable with tolerance=1 * * @param range * @param tolerance * @return */ public boolean canJoin(IntRange range, int tolerance) { return Math.abs(this.maxval - range.minval) <= tolerance; } public void setMax(int max) { if (max >= this.minval) { this.maxval = max; } } public void setMin(int min) { if (min <= this.maxval) { this.minval = min; } } } class IntRangeComparator implements Comparator{ @Override public int compare(IntRange o1, IntRange o2) { if (o1 == null || o2 == null) return -1; return o1.getMin() - o2.getMin(); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy