/******************************************************************************* * Copyright (c) 2002 - 2006 IBM Corporation. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package com.ibm.wala.util.intset; import java.util.Iterator; import java.util.NoSuchElementException; import java.util.StringTokenizer; import java.util.TreeSet; import com.ibm.wala.util.debug.Assertions; import com.ibm.wala.util.debug.UnimplementedError; /** * A sparse ordered, duplicate-free, fully-encapsulated set of integers; not necessary mutable */ public class SparseIntSet implements IntSet { private final static int SINGLETON_CACHE_SIZE = 5000; private final static SparseIntSet[] singletonCache = new SparseIntSet[SINGLETON_CACHE_SIZE]; static { for (int i = 0; i < SINGLETON_CACHE_SIZE; i++) { singletonCache[i] = new SparseIntSet(new int[] { i }); } } // TODO: I'm not thrilled with exposing these to subclasses, but // it seems expedient for now. /** * The backing store of int arrays */ protected int[] elements; /** * The number of entries in the backing store that are valid. */ protected int size = 0; protected SparseIntSet(int size) { elements = new int[size]; this.size = size; } /** * Subclasses should use this with extreme care. Do not allow the backing array to escape elsewhere. */ protected SparseIntSet(int[] backingArray) { if (backingArray == null) { throw new IllegalArgumentException("backingArray is null"); } elements = backingArray; this.size = backingArray.length; } /** * Subclasses should use this with extreme care. */ public SparseIntSet() { elements = null; this.size = 0; } protected SparseIntSet(SparseIntSet S) { cloneState(S); } private void cloneState(SparseIntSet S) { if (S.elements != null) { elements = S.elements.clone(); } else { elements = null; } this.size = S.size; } public SparseIntSet(IntSet S) throws IllegalArgumentException { if (S == null) { throw new IllegalArgumentException("S == null"); } if (S instanceof SparseIntSet) { cloneState((SparseIntSet) S); } else { elements = new int[S.size()]; size = S.size(); S.foreach(new IntSetAction() { private int index = 0; @Override public void act(int i) { elements[index++] = i; } }); } } /** * Does this set contain value x? * * @see com.ibm.wala.util.intset.IntSet#contains(int) */ @Override public final boolean contains(int x) { if (elements == null) { return false; } return IntSetUtil.binarySearch(elements, x, 0, size - 1) >= 0; } /** * @return index i s.t. elements[i] == x, or -1 if not found. */ public final int getIndex(int x) { if (elements == null) { return -1; } return IntSetUtil.binarySearch(elements, x, 0, size - 1); } @Override public final int size() { return size; } @Override public final boolean isEmpty() { return size == 0; } public final int elementAt(int idx) throws NoSuchElementException { if (idx < 0) { throw new IllegalArgumentException("invalid idx: " + idx); } if (elements == null || idx >= size) { throw new NoSuchElementException("Index: " + idx); } return elements[idx]; } private boolean sameValueInternal(SparseIntSet that) { if (size != that.size) { return false; } else { for (int i = 0; i < size; i++) { if (elements[i] != that.elements[i]) { return false; } } return true; } } @Override public boolean sameValue(IntSet that) throws IllegalArgumentException, UnimplementedError { if (that == null) { throw new IllegalArgumentException("that == null"); } if (that instanceof SparseIntSet) { return sameValueInternal((SparseIntSet) that); } else if (that instanceof BimodalMutableIntSet) { return that.sameValue(this); } else if (that instanceof BitVectorIntSet) { return that.sameValue(this); } else if (that instanceof MutableSharedBitVectorIntSet) { return sameValue(((MutableSharedBitVectorIntSet) that).makeSparseCopy()); } else { Assertions.UNREACHABLE(that.getClass().toString()); return false; } } /** * @return true iff <code>this</code> is a subset of <code>that</code>. * * Faster than: <code>this.diff(that) == EMPTY</code>. */ private boolean isSubsetInternal(SparseIntSet that) { if (elements == null) return true; if (that.elements == null) return false; if (this.equals(that)) return true; if (this.sameValue(that)) { return true; } int[] ar = this.elements; int ai = 0; int al = size; int[] br = that.elements; int bi = 0; int bl = that.size; while (ai < al && bi < bl) { int cmp = (ar[ai] - br[bi]); // (fail when element only found in 'a') if (cmp > 0) { // a greater bi++; } else if (cmp < 0) { // b greater return false; } else { ai++; bi++; } } if (bi == bl && ai < al) { // we ran off the end of b without finding an a return false; } return true; } /** * Compute the asymmetric difference of two sets, a \ b. */ public static SparseIntSet diff(SparseIntSet A, SparseIntSet B) { return new SparseIntSet(diffInternal(A, B)); } public static int[] diffInternal(SparseIntSet A, SparseIntSet B) { if (A == null) { throw new IllegalArgumentException("A is null"); } if (B == null) { throw new IllegalArgumentException("B is null"); } if (A.isEmpty()) { return new int[0]; } else if (B.isEmpty()) { int[] newElts = new int[A.size]; System.arraycopy(A.elements, 0, newElts, 0, A.size); return newElts; } else if (A.equals(B)) { return new int[0]; } else if (A.sameValue(B)) { return new int[0]; } int[] ar = A.elements; int ai = 0; int al = A.size; int[] br = B.elements; int bi = 0; int bl = B.size; int[] cr = new int[al]; // allocate enough (i.e. too much) int ci = 0; while (ai < al && bi < bl) { int cmp = (ar[ai] - br[bi]); // (accept element when only found in 'a') if (cmp > 0) { // a greater bi++; } else if (cmp < 0) { // b greater cr[ci++] = ar[ai]; ai++; } else { ai++; bi++; } } // append a's tail if any if (ai < al) { int tail = al - ai; System.arraycopy(ar, ai, cr, ci, tail); ci += tail; } // now compact cr to 'just enough' ar = new int[ci]; System.arraycopy(cr, 0, ar, 0, ci); // ar := cr return ar; } @Override public String toString() { StringBuffer sb = new StringBuffer(6 * size); sb.append("{ "); if (elements != null) { for (int ii = 0; ii < size; ii++) { sb.append(elements[ii]); sb.append(" "); } } sb.append("}"); return sb.toString(); } /** * Reverse of toString(): "{2,3}" -> [2,3] * @throws IllegalArgumentException if str is null */ public static int[] parseIntArray(String str) { if (str == null) { throw new IllegalArgumentException("str is null"); } int len = str.length(); if (len == 0 || str.charAt(0) != '{' || str.charAt(len - 1) != '}') { throw new IllegalArgumentException(str); } str = str.substring(1, len - 1); StringTokenizer tok = new StringTokenizer(str, " ,"); // XXX not very efficient: TreeSet<Integer> set = new TreeSet<Integer>(); while (tok.hasMoreTokens()) { set.add(Integer.decode(tok.nextToken())); } int[] result = new int[set.size()]; int i = 0; for (Iterator<Integer> it = set.iterator(); it.hasNext();) { Integer I = it.next(); result[i++] = I.intValue(); } return result; } public static SparseIntSet singleton(int i) { if (i >= 0 && i < SINGLETON_CACHE_SIZE) { return singletonCache[i]; } else { return new SparseIntSet(new int[] { i }); } } public static SparseIntSet pair(int i, int j) { if (i == j) { return SparseIntSet.singleton(i); } if (j > i) { return new SparseIntSet(new int[] { i, j }); } else { return new SparseIntSet(new int[] { j, i }); } } @Override public IntSet intersection(IntSet that) { if (that == null) { throw new IllegalArgumentException("that == null"); } if (that instanceof SparseIntSet) { MutableSparseIntSet temp = MutableSparseIntSet.make(this); temp.intersectWith((SparseIntSet) that); return temp; } else if (that instanceof BitVectorIntSet) { SparseIntSet s = ((BitVectorIntSet) that).toSparseIntSet(); MutableSparseIntSet temp = MutableSparseIntSet.make(this); temp.intersectWith(s); return temp; } else if (that instanceof MutableSharedBitVectorIntSet) { MutableSparseIntSet temp = MutableSparseIntSet.make(this); temp.intersectWith(that); return temp; } else { // this is really slow. optimize as needed. MutableSparseIntSet temp = MutableSparseIntSet.makeEmpty(); for (IntIterator it = intIterator(); it.hasNext();) { int x = it.next(); if (that.contains(x)) { temp.add(x); } } return temp; } } /* * @see com.ibm.wala.util.intset.IntSet#union(com.ibm.wala.util.intset.IntSet) */ @Override public IntSet union(IntSet that) { MutableSparseIntSet temp = new MutableSparseIntSet(); temp.addAll(this); temp.addAll(that); return temp; } /* * @see com.ibm.wala.util.intset.IntSet#iterator() */ @Override public IntIterator intIterator() { return new IntIterator() { int i = 0; @Override public boolean hasNext() { return (i < size); } @Override public int next() throws NoSuchElementException { if (elements == null) { throw new NoSuchElementException(); } return elements[i++]; } }; } /** * @return the largest element in the set */ @Override public final int max() throws IllegalStateException { if (elements == null) { throw new IllegalStateException("Illegal to ask max() on an empty int set"); } return (size > 0) ? elements[size - 1] : -1; } /* * @see com.ibm.wala.util.intset.IntSet#foreach(com.ibm.wala.util.intset.IntSetAction) */ @Override public void foreach(IntSetAction action) { if (action == null) { throw new IllegalArgumentException("null action"); } for (int i = 0; i < size; i++) action.act(elements[i]); } /* * @see com.ibm.wala.util.intset.IntSet#foreach(com.ibm.wala.util.intset.IntSetAction) */ @Override public void foreachExcluding(IntSet X, IntSetAction action) { if (action == null) { throw new IllegalArgumentException("null action"); } for (int i = 0; i < size; i++) { if (!X.contains(elements[i])) { action.act(elements[i]); } } } /** * @return a new sparse int set which adds j to s * @throws IllegalArgumentException if s is null */ public static SparseIntSet add(SparseIntSet s, int j) { if (s == null) { throw new IllegalArgumentException("s is null"); } if (s.elements == null) { return SparseIntSet.singleton(j); } SparseIntSet result = new SparseIntSet(s.size + 1); int k = 0; int m = 0; while (k < s.elements.length && s.elements[k] < j) { result.elements[k++] = s.elements[m++]; } if (k == s.size) { result.elements[k] = j; } else { if (s.elements[k] == j) { result.size--; } else { result.elements[k++] = j; } while (k < result.size) { result.elements[k++] = s.elements[m++]; } } return result; } /* * @see com.ibm.wala.util.intset.IntSet#isSubset(com.ibm.wala.util.intset.IntSet) */ @Override public boolean isSubset(IntSet that) { if (that == null) { throw new IllegalArgumentException("null that"); } if (that instanceof SparseIntSet) { return isSubsetInternal((SparseIntSet) that); } else if (that instanceof BitVectorIntSet) { return isSubsetInternal((BitVectorIntSet) that); } else { // really slow. optimize as needed. for (IntIterator it = intIterator(); it.hasNext();) { if (!that.contains(it.next())) { return false; } } return true; } } private boolean isSubsetInternal(BitVectorIntSet set) { for (int i = 0; i < size; i++) { if (!set.contains(elements[i])) { return false; } } return true; } /* * @see com.ibm.wala.util.intset.IntSet#containsAny(com.ibm.wala.util.intset.IntSet) */ @Override public boolean containsAny(IntSet set) { if (set instanceof SparseIntSet) { return containsAny((SparseIntSet) set); } else if (set instanceof BimodalMutableIntSet) { return set.containsAny(this); } else { for (int i = 0; i < size; i++) { if (set.contains(elements[i])) { return true; } } return false; } } public boolean containsAny(SparseIntSet set) throws IllegalArgumentException { if (set == null) { throw new IllegalArgumentException("set == null"); } int i = 0; for (int j = 0; j < set.size; j++) { int x = set.elements[j]; while (i < size && elements[i] < x) { i++; } if (i == size) { return false; } else if (elements[i] == x) { return true; } } return false; } /** * @return contents as an int[] */ public int[] toIntArray() { int[] result = new int[size]; if (size > 0) { System.arraycopy(elements, 0, result, 0, size); } return result; } }