package com.onionnetworks.util; import java.util.*; import java.io.*; import java.text.ParseException; /** * This class represents a set of integers in a compact form by using ranges. * This is essentially equivilent to run length encoding of a bitmap-based * set and thus works very well for sets with long runs of integers, but is * quite poor for sets with very short runs. * * This class is similar in flavor to Perl's IntSpan at * http://world.std.com/~swmcd/steven/perl/lib/Set/IntSpan/ * * The Ranges use negative and positive infinity so there should be no * border issues with standard set operations and they should behave * exactly as you'd expect from your set identities. * * While the data structure itself should be quite efficient for its intended * use, the actual implementation could be heavily optimized beyond what I've * done, feel free to improve it. * * @author Justin F. Chapweske */ public class RangeSet { public static final int DEFAULT_CAPACITY = 16; boolean posInf, negInf; int rangeCount; long[] ranges; /** * Creates a new empty RangeSet */ public RangeSet() { ranges = new long[DEFAULT_CAPACITY * 2]; } /** * Creates a new RangeSet and adds the provided Range * @param r The range to add. */ public RangeSet(Range r) { this(); add(r); } /** * @param rs The set with which to union with this set. * @return A new set that represents the union of this and the passed set. */ public RangeSet union(RangeSet rs) { // This should be rewritten to interleave the additions so that there // is fewer midlist insertions. RangeSet result = new RangeSet(); result.add(this); result.add(rs); return result; } /** * @param rs The set with which to intersect with this set. * @return new set that represents the intersct of this and the passed set. */ public RangeSet intersect(RangeSet rs) { RangeSet result = complement(); result.add(rs.complement()); return result.complement(); } /** * @return The complement of this set, make sure to check for positive * and negative infinity on the returned set. */ public RangeSet complement() { RangeSet rs = new RangeSet(); if (isEmpty()) { rs.add(new Range(true,true)); } else { if (!negInf) { rs.add(new Range(true,ranges[0]-1)); } for (int i=0;i<rangeCount-1;i++) { rs.add(ranges[i*2+1]+1,ranges[i*2+2]-1); } if (!posInf) { rs.add(new Range(ranges[(rangeCount-1)*2+1]+1,true)); } } return rs; } /** * @param i The integer to check to see if it is in this set.. * @return true if i is in the set. */ public boolean contains(long i) { int pos = binarySearch(i); if (pos > 0) { return true; } pos = -(pos+1); if (pos % 2 == 0) { return false; } else { return true; } } /** * //FIX unit test * Checks to see if this set contains all of the elements of the Range. * * @param r The Range to see if this RangeSet contains it. * @return true If every element of the Range is within this set. */ public boolean contains(Range r) { RangeSet rs = new RangeSet(); rs.add(r); return intersect(rs).equals(rs); } /** * Add all of the passed set's elements to this set. * * @param rs The set whose elements should be added to this set. */ public void add(RangeSet rs) { for (Iterator it=rs.iterator();it.hasNext();) { add((Range) it.next()); } } /** * Add this range to the set. * * @param r The range to add */ public void add(Range r) { if (r.isMinNegInf()) { negInf = true; } if (r.isMaxPosInf()) { posInf = true; } add(r.getMin(),r.getMax()); } /** * Add a single integer to this set. * * @param i The int to add. */ public void add(long i) { add(i,i); } /** * Add a range to the set. * @param min The min of the range (inclusive) * @param max The max of the range (inclusive) */ public void add(long min, long max) { if (min > max) { throw new IllegalArgumentException ("min cannot be greater than max"); } if (rangeCount == 0) { // first value. insert(min,max,0); return; } // This case should be the most common (insert at the end) so we will // specifically check for it. Its +1 so that we make sure its not // adjacent. Do the MIN_VALUE check to make sure we don't overflow // the long. if (min != Long.MIN_VALUE && min-1 > ranges[(rangeCount-1)*2+1]) { insert(min,max,rangeCount); return; } // minPos and maxPos represent inclusive brackets around the various // ranges that this new range encompasses. Anything within these // brackets should be folded into the new range. int minPos = getMinPos(min); int maxPos = getMaxPos(max); // minPos and maxPos will switch order if we are either completely // within another range or completely outside of any ranges. if (minPos > maxPos) { if (minPos % 2 == 0) { // outside of any ranges, insert. insert(min,max,minPos/2); } else { // completely inside a range, nop } } else { combine(min,max,minPos/2,maxPos/2); } } public void remove(RangeSet r) { for (Iterator it=r.iterator();it.hasNext();) { remove((Range) it.next()); } } public void remove(Range r) { //FIX absolutely horrible implementation. RangeSet rs = new RangeSet(); rs.add(r); rs = intersect(rs.complement()); ranges = rs.ranges; rangeCount = rs.rangeCount; posInf = rs.posInf; negInf = rs.negInf; } public void remove(long i) { remove(new Range(i,i)); } public void remove(long min, long max) { remove(new Range(min,max)); } /** * @return An iterator of Range objects that this RangeSet contains. */ public Iterator iterator() { ArrayList l = new ArrayList(rangeCount); for (int i=0;i<rangeCount;i++) { if (rangeCount == 1 && negInf && posInf) { l.add(new Range(true,true)); } else if (i == 0 && negInf) { l.add(new Range(true,ranges[i*2+1])); } else if (i == rangeCount-1 && posInf) { l.add(new Range(ranges[i*2],true)); } else { l.add(new Range(ranges[i*2],ranges[i*2+1])); } } return l.iterator(); } /** * @return The number of integers in this set, -1 if infinate. */ public long size() { if (negInf || posInf) { return -1; } long result = 0; for (Iterator it=iterator();it.hasNext();) { result+=((Range) it.next()).size(); } return result; } /** * @return true If the set doesn't contain any integers or ranges. */ public boolean isEmpty() { return rangeCount == 0; } /** * Parse a set of ranges seperated by commas. * * @see Range * * @param s The String to parse * @return The resulting range * @throws ParseException */ public static RangeSet parse(String s) throws ParseException { RangeSet rs = new RangeSet(); for (StringTokenizer st = new StringTokenizer(s,","); st.hasMoreTokens();) { rs.add(Range.parse(st.nextToken())); } return rs; } public int hashCode() { int result = 0; for (int i = 0; i < rangeCount*2; i++) { result = (int) (91*result + ranges[i]); } return result; } public boolean equals(Object obj) { if (obj instanceof RangeSet) { RangeSet rs = (RangeSet) obj; if (negInf == rs.negInf && posInf == rs.posInf && rangeCount == rs.rangeCount && Util.arraysEqual(ranges,0,rs.ranges,0,rangeCount*2)) { return true; } } return false; } /** * Outputs the Range in a manner that can be used to directly create * a new range with the "parse" method. */ public String toString() { StringBuffer sb = new StringBuffer(); for (Iterator it=iterator();it.hasNext();) { sb.append(it.next().toString()); if (it.hasNext()) { sb.append(","); } } return sb.toString(); } public Object clone() { RangeSet rs = new RangeSet(); rs.ranges = new long[ranges.length]; System.arraycopy(ranges,0,rs.ranges,0,ranges.length); rs.rangeCount = rangeCount; rs.posInf = posInf; rs.negInf = negInf; return rs; } private void combine(long min, long max, int minRange, int maxRange) { // Fill in the new values into the "leftmost" range. ranges[minRange*2] = Math.min(min,ranges[minRange*2]); ranges[minRange*2+1] = Math.max(max,ranges[maxRange*2+1]); // shrink if necessary. if (minRange != maxRange && maxRange != rangeCount-1) { System.arraycopy(ranges,(maxRange+1)*2,ranges,(minRange+1)*2, (rangeCount-1-maxRange)*2); } rangeCount -= maxRange-minRange; } /** * @return the position of the min element within the ranges. */ private int getMinPos(long min) { // Search for min-1 so that adjacent ranges are included. int pos = binarySearch(min == Long.MIN_VALUE ? min : min-1); return pos >= 0 ? pos : -(pos+1); } /** * @return the position of the max element within the ranges. */ private int getMaxPos(long max) { // Search for max+1 so that adjacent ranges are included. int pos = binarySearch(max == Long.MAX_VALUE ? max : max+1); // Return pos-1 if there isn't a direct hit because the max // pos is inclusive. return pos >= 0 ? pos : (-(pos+1))-1; } /** * @see java.util.Arrays#binarySearch */ private int binarySearch(long key) { int low = 0; int high = (rangeCount*2)-1; while (low <= high) { int mid =(low + high)/2; long midVal = ranges[mid]; if (midVal < key) { low = mid + 1; } else if (midVal > key) { high = mid - 1; } else { return mid; // key found } } return -(low + 1); // key not found. } private void insert(long min, long max, int rangeNum) { // grow the array if necessary. if (ranges.length == rangeCount*2) { long[] newRanges = new long[ranges.length*2]; System.arraycopy(ranges,0,newRanges,0,ranges.length); ranges = newRanges; } if (rangeNum != rangeCount) { System.arraycopy(ranges,rangeNum*2,ranges,(rangeNum+1)*2, (rangeCount-rangeNum)*2); } ranges[rangeNum*2] = min; ranges[rangeNum*2+1] = max; rangeCount++; } public static final void main(String[] args) throws Exception { RangeSet rs = RangeSet.parse("5-10,15-20,25-30"); BufferedReader br = new BufferedReader(new InputStreamReader (System.in)); while (true) { System.out.println(rs.toString()); String s = br.readLine(); if (s.charAt(0) == '~') { rs = rs.complement(); } else if (s.charAt(0) == 'i') { rs = rs.intersect(RangeSet.parse(br.readLine())); } else { rs.add(RangeSet.parse(s)); } } } }