/** * Copyright 2015 Santhosh Kumar Tekuri * * The JLibs authors license this file to you 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 jlibs.core.util; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; /** * This class represents integer range. * * @author Santhosh Kumar T */ public class Range{ public final int min; public final int max; /** * Note that both min and max are inclusive */ public Range(int min, int max){ if(min>max) throw new IllegalArgumentException("invalid range: ["+min+", "+max+']'); this.min = min; this.max = max; } @Override public int hashCode(){ return min+max; } @Override public boolean equals(Object obj){ if(obj instanceof Range){ Range that = (Range)obj; return this.min==that.min && this.max==that.max; }else return false; } @Override public String toString(){ return "["+min+", "+max+']'; } /** * return true if this range is before given number * <pre> * ------------ o * </pre> */ public boolean before(int x){ return x>max; } /** * returns true if this range is after given number * <pre> * o -------------- * </pre> */ public boolean after(int x){ return x<min; } /** * returns true if this range contains given number * <pre> * ------o-------- * </pre> */ public boolean contains(int x){ return !before(x) && !after(x); } /** * tells the position this range with respect to given range. * <p><br> * the return value is boolean array of size 3.<br> * <blockquote> * 1st boolean ==> true if some portion of this range is before given range<br> * 2nd boolean ==> true if this range intersects with given range<br> * 3rd boolean ==> true if some portion of this range is after given range * </blockquote> * ideally, you can remebers these boolean as { before, inside, after } */ public boolean[] position(Range that){ boolean before = that.after(min); boolean after = that.before(max); boolean inside; if(before && after) inside = true; else if(!before && !after) inside = true; else if(before) inside = that.contains(max); else inside = that.contains(min); return new boolean[]{ before, inside, after }; } /** * this method splits this range into 3 regions with respect to given range * <p><br> * the return value is Range array of size 3.<br> * <blockquote> * 1st range ==> the portion of this range that is before given range<br> * 2nd range ==> the portion of range that is common to this range and given range<br> * 3rd range ==> the portion of this range that is after given range. * </blockquote> * Note that the values in returned array can be null, if there is no range satifying * the requirement. */ public Range[] split(Range that){ boolean position[] = position(that); Range before = null; if(position[0]) before = new Range(this.min, Math.min(this.max, that.min-1)); Range after = null; if(position[2]) after = new Range(Math.max(this.min, that.max+1), this.max); Range inside = null; if(position[1]) inside = new Range(Math.max(this.min, that.min), Math.min(this.max, that.max)); return new Range[]{ before, inside, after }; } /** * return the portion of range that is common to this range and given range.<br> * If there is nothing common, then null is returned. */ public Range intersection(Range that){ return split(that)[1]; } /** * returns union of this range with given range.<br> * if both ranges are adjacent/intersecting to each other, then the * returned array will have only one range. otherwise the returned array * will have two ranges in sorted order. */ public Range[] union(Range that){ if(this.min>that.min) return that.union(this); // this ------------- // that -------------- // that ------- if(contains(that.min)) return new Range[]{ new Range(this.min, Math.max(this.max, that.max)) }; // this ----------- // that -------------- return new Range[]{ this, that }; } /** * returns the portion(s) of this range that are not present in given range. * maximum size of returned array is 2. and the array is sorted. */ public Range[] minus(Range that){ Range split[] = split(that); if(split[0]==null && split[2]==null) return new Range[0]; if(split[0]!=null && split[2]!=null) return new Range[]{ split[0], split[2] }; return new Range[]{ split[0]!=null ? split[0] : split[2] }; } /*-------------------------------------------------[ Helpers ]---------------------------------------------------*/ public static List<Range> union(List<Range> ranges){ ranges = new ArrayList<Range>(ranges); Collections.sort(ranges, new Comparator<Range>(){ @Override public int compare(Range r1, Range r2){ return r1.min-r2.min; } }); List<Range> union = new ArrayList<Range>(); for(Range r: ranges){ if(union.isEmpty()) union.add(r); else CollectionUtil.addAll(union, union.remove(union.size() - 1).union(r)); } return union; } public static List<Range> intersection(List<Range> list1, List<Range> list2){ list1 = union(list1); list2 = union(list2); List<Range> intersection = new ArrayList<Range>(); for(Range r1: list1){ for(Range r2: list2){ Range r = r1.intersection(r2); if(r!=null) intersection.add(r); } } return union(intersection); } public static List<Range> minus(List<Range> list1, List<Range> list2){ list1 = union(list1); list2 = union(list2); for(Range r2: list2){ List<Range> temp = new ArrayList<Range>(); for(Range r1: list1) CollectionUtil.addAll(temp, r1.minus(r2)); list1 = temp; } return union(list1); } public static boolean same(List<Range> list1, List<Range> list2){ list1 = union(list1); list2 = union(list2); return list1.equals(list2); } }