// // Licenced under GPLv2, see licence.txt // (c) Jan Kotek, // package org.asterope.healpix; import java.io.Externalizable; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; import java.util.Arrays; import java.util.Iterator; import java.util.NoSuchElementException; /** * This class represents range based sets of long values. * First and last boundaries are stored in sorted long[] array so it consumes very little place * 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. *

* Is readonly, so is thread safe. *

* To construct new LongRangeSet use {@link LongRangeSetBuilder} if * our values are sorted. Or use {@link LongSet} if our values are unsorted. *

* Inspired by Justin F. Chapweske and Soren Bak * @author Jan Kotek */ public class LongRangeSet implements Externalizable, Iterable{ private static final long serialVersionUID = -7543399451387806240L; /** sorted ranges, even is first, odd is last */ protected long[] ranges; /** empty constructor for serialization*/ public LongRangeSet() { } /** * Construct new LongRangeSet from given values *

* Dont use directly, use {@link LongRangeSetBuilder} instead *

* This constructor makes integrity check and copyes values into new array. * When LongRangeSet is constructed inside LongRangeSet this operation can be * skipped and set can be constructed way faster. * */ public LongRangeSet(long[] values, int size){ if(size == 0){ ranges = new long[0]; return; } if(size%2!=0) throw new IllegalArgumentException("not divide by 2"); if(size<1) throw new IllegalArgumentException("et least one range is needed"); if(values.lengthlast) throw new IllegalArgumentException("first > last"); if(oldLast+1>=first && first!=Long.MIN_VALUE) throw new IllegalArgumentException("values are not sorted at oldLast: "+oldLast+", first: "+first); //move to new array ranges[i*2] = first; ranges[i*2+1] = last; } } /** * @return Iterator over all longs in this set */ public LongIterator longIterator() { if(isEmpty()) return new LongIterator(){ public boolean hasNext() { return false; } public long next() { throw new NoSuchElementException(); }}; return new LongIterator(){ int pos = 0; long value = first(); public boolean hasNext() { return pos = ranges.length || value>ranges[pos+1]) throw new NoSuchElementException(); long ret = value; //move to next position if(value=ranges.length) throw new NoSuchElementException(); return ranges[pos]; } public long last() { if(pos<0) throw new IllegalAccessError("Call moveToNext() first"); if(pos>=ranges.length) throw new NoSuchElementException(); return ranges[pos+1]; } }; } public Iterator iterator() { return new Iterator(){ LongIterator iter =longIterator(); public boolean hasNext() { return iter.hasNext(); } public Long next() { return iter.next(); } public void remove() { throw new UnsupportedOperationException(); } }; } /** * @return first element in set * @throws NoSuchElementException if set isEmpty() */ public long first() { if(isEmpty()) throw new NoSuchElementException(); return ranges[0]; } /** * @return last element in set * @throws NoSuchElementException if set isEmpty() */ public long last() { if(isEmpty()) throw new NoSuchElementException(); return ranges[ranges.length-1]; } /** * @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 = Arrays.binarySearch(ranges,i); if (pos > 0) { return true; } pos = -(pos+1); return pos % 2 != 0; } public boolean containsAll(long first, long last){ if(first>last) throw new IllegalArgumentException("First is bigger then last"); if(isEmpty() || last < first() || first>last()) return false; int firstIndex = Arrays.binarySearch(ranges, first); int lastIndex = Arrays.binarySearch(ranges, last); if(firstIndex>=0 && lastIndex>=0) return lastIndex - firstIndex <2; return false; } public boolean containsAny(long first, long last){ if(first>last) throw new IllegalArgumentException("First is bigger then last"); if(isEmpty() || last < first() || first>last()) return false; int firstIndex = Arrays.binarySearch(ranges, first); int lastIndex = Arrays.binarySearch(ranges, last); //System.out.println(firstIndex + " - "+lastIndex); if(firstIndex <0 && lastIndex<0 && firstIndex%2==-1 && firstIndex == lastIndex) return false; return true; } public boolean containsAll(LongIterator iter){ while(iter.hasNext()) if(!contains(iter.next())) return false; return true; } public boolean containsAny(LongIterator iter){ while(iter.hasNext()) if(contains(iter.next())) return true; return false; } public boolean containsAll(LongRangeIterator iter){ while(iter.moveToNext()) if(!containsAll(iter.first(), iter.last())) return false; return true; } public boolean containsAny(LongRangeIterator iter){ while(iter.moveToNext()) if(containsAny(iter.first(), iter.last())) return true; return false; } /** * Converts to LongSet which can be modified * @return LongSet */ public LongSet toLongSet() { LongSet ret = new LongSet(); ret.addAll(longIterator()); return ret; } /** * @return number of longs (pixels) in this set. !!NOT number of ranges!! */ public long size() { long size = 0; LongRangeIterator iter = rangeIterator(); while(iter.moveToNext()){ size+=1 + iter.last() - iter.first(); } return size; } /** * @return number of ranges in this set */ public int rangeCount(){ return ranges.length/2; } /** * Convert all items in range set to array. * With large set, this method will fail with OutOfMemoryException * @return array of elements in collection */ public long[] toArray(){ long[] ret = new long[(int)size()]; LongIterator iter = longIterator(); for(int i=0;iter.hasNext();i++) ret[i] = iter.next(); return ret; } public String toString() { StringBuilder s = new StringBuilder(); s.append('['); LongRangeIterator iter = rangeIterator(); while(iter.moveToNext()){ if (s.length() > 1) s.append(','); s.append(iter.first()); s.append('-'); s.append(iter.last()); } s.append(']'); return s.toString(); } public int hashCode() { final int prime = 31; int result = 1; result = prime * result + Arrays.hashCode(ranges); return result; } public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (!(obj instanceof LongRangeSet)) return false; LongRangeSet other = (LongRangeSet) obj; if (!Arrays.equals(ranges, other.ranges)) return false; return true; } /** * Create new LongRangeSet with complement (inversion) of values in this set. * Bounds of complement are Long.MIN_VALUE and Long.MAX_VALUE * if operation is called twice, original set is produced *

* This operation is FAST. Requires only one traversal of ranges. * It does not decompress RangeSet to pixels. *

* This operation does not modify original collection. * * @return inverted LongRangeSet */ public LongRangeSet complement() { LongRangeSetBuilder b = new LongRangeSetBuilder(ranges.length +2); long last = Long.MIN_VALUE; LongRangeIterator iter = rangeIterator(); while(iter.moveToNext()){ if(iter.first() != Long.MIN_VALUE) //was already starting with Long.MIN_VALUE, ignore first range b.appendRange(last, iter.first()-1); last = iter.last()+1; } //close if(//case with completely empty set last==Long.MIN_VALUE && b.size()==0 || //case when last was not Long.MAX_VALUE (there is +1 overflow) and close prev value last!=Long.MIN_VALUE && b.size()>0 ) b.appendRange(last,Long.MAX_VALUE); return b.build(); } /** * Create new LongRangeSet which contains union of values from * original set and parameter *

* This operation is FAST. Requires only one traversal of ranges. * It does not decompress RangeSet to pixels. *

* This operation does not modify original collection. * * * @param rs LongRangeSet to make union with * @return LongRangeSet contains union of original set and parameter set */ public LongRangeSet union(LongRangeSet rs) { LongRangeIterator it1 = rangeIterator(); LongRangeIterator it2 = rs.rangeIterator(); LongRangeSetBuilder rsb = new LongRangeSetBuilder(); //boolean indicates if iterator have more data boolean run1 = it1.moveToNext(); boolean run2 = it2.moveToNext(); //problem is that data appended in builder must be sorted //so use two iterators at the same time and produce sorted result while(run1 || run2){ //repeat until any of iterators have data if(run1 && (!run2 || it1.last() * This operation is FAST. Requires only one traversal of ranges. * It does not decompress RangeSet to pixels. *

* This operation does not modify original collection. * @param rs The set with which to intersect with this set. * @return new set that represents the intersect of original and parameter set */ public LongRangeSet intersect(LongRangeSet rs) { if(isEmpty()) return rs; if(rs.isEmpty()) return this; if(first()>rs.last() || last()it2.last()){ //second range inside first rsb.appendRange(it2.first(), it2.last()); run2 = it2.moveToNext(); }else if(it2.first()it1.last()){ //first range inside first rsb.appendRange(it1.first(), it1.last()); run1 = it1.moveToNext(); }else { //overlap long maxFirst = Math.max(it1.first(), it2.first()); long minLast = Math.min(it1.last(), it2.last()); rsb.appendRange(maxFirst, minLast); if(it1.last() * [1-5].substract[4-6] == [1-3] *

* This operation is FAST. Requires only one traversal of ranges. * It does not decompress RangeSet to pixels. *

* This operation does not modify original collection. * @ p substract this set from original * @return result of substraction */ public LongRangeSet substract(LongRangeSet rs){ //quick check on boundaries if(isEmpty() || rs.isEmpty() || first()>rs.last() || last()