PageRenderTime 48ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

/source/org/limewire/collection/IntSet.java

https://github.com/zootella/Lost-in-the-Space
Java | 419 lines | 244 code | 54 blank | 121 comment | 44 complexity | 1a6be88fe4419cd21371f3aefdf10cb3 MD5 | raw file
  1. package org.limewire.collection;
  2. import java.util.ArrayList;
  3. import java.util.List;
  4. import java.util.NoSuchElementException;
  5. import java.util.Set;
  6. /**
  7. * Represents a set of distinct integers.
  8. * Like {@link Set}, <code>IntSet</code> is <b>not synchronized</b>.
  9. * <p>
  10. * Optimized to have an extremely compact representation
  11. * when the set is "dense", i.e., has many sequential elements. For example {1,
  12. * 2} and {1, 2, ..., 1000} require the same amount of space. All retrieval
  13. * operations run in O(log n) time, where n is the size of the set. Insertion
  14. * operations may be slower.
  15. * <p>
  16. * All methods have the same specification as the <code>Set</code> class, except
  17. * that values are restricted to int' for the reason described above. For
  18. * this reason, methods are not specified below.
  19. * <p>
  20. * This class is not thread-safe.
  21. * <pre>
  22. IntSet s = new IntSet(10);
  23. s.add(1); s.add(1);
  24. s.add(3); s.add(4); s.add(5);
  25. s.add(7);
  26. System.out.println("Set is " + s);
  27. s.add(2);
  28. System.out.println("Set is " + s);
  29. s.remove(3);
  30. System.out.println("Set is " + s);
  31. Output:
  32. Set is [1, 3-5, 7]
  33. Set is [1-5, 7]
  34. Set is [1-2, 4-5, 7]
  35. * </pre>
  36. */
  37. public class IntSet {
  38. /**
  39. * Our current implementation consists of a list of disjoint intervals,
  40. * sorted by starting location. As an example, the set {1, 3, 4, 5, 7} is
  41. * represented by
  42. * [1, 3-5, 7]
  43. * Adding 2 turns the representation into
  44. * [1-5, 7]
  45. * Note that [1-2 ,3-5, 7] is not allowed by the invariant below.
  46. * Continuing with the example, removing 3 turns the rep into
  47. * [1-2, 4-5, 7]
  48. *
  49. * We use a sorted List instead of a TreeSet because it has a more compact
  50. * memory footprint, and memory is at a premium here. It also makes
  51. * implementation much easier. Unfortunately it means that insertion
  52. * and some set operations are more expensive because memory must be
  53. * allocated and copied.
  54. *
  55. * INVARIANT: for all i<j, list[i].high < (list[j].low-1)
  56. */
  57. private ArrayList<Interval> list;
  58. /**
  59. * The size of this.
  60. *
  61. * INVARIANT: size==sum over all i of (get(i).high-get(i).low+1)
  62. */
  63. private int size=0;
  64. /** The interval from low to high, inclusive on both ends. */
  65. private static class Interval {
  66. /** INVARIANT: low<=high */
  67. int low;
  68. int high;
  69. /** @requires that low<=high */
  70. Interval(int low, int high) {
  71. this.low=low;
  72. this.high=high;
  73. }
  74. Interval(int singleton) {
  75. this.low=singleton;
  76. this.high=singleton;
  77. }
  78. @Override
  79. public String toString() {
  80. if (low==high)
  81. return String.valueOf(low);
  82. else
  83. return String.valueOf(low)+"-"+String.valueOf(high);
  84. }
  85. }
  86. /** Checks rep invariant. */
  87. protected void repOk() {
  88. if (list.size()<2)
  89. return;
  90. int countedSize=0;
  91. for (int i=0; i<(list.size()-1); i++) {
  92. Interval lower=get(i);
  93. countedSize+=(lower.high-lower.low+1);
  94. Interval higher=get(i+1);
  95. assert lower.low<=lower.high :
  96. "Backwards interval: "+toString();
  97. assert lower.high<(higher.low-1) :
  98. "Touching intervals: "+toString();
  99. }
  100. //Don't forget to check last interval.
  101. Interval last=get(list.size()-1);
  102. assert last.low<=last.high :
  103. "Backwards interval: " + this;
  104. countedSize+=(last.high-last.low+1);
  105. assert countedSize==size :
  106. "Bad size. Should be "+countedSize+" not "+size;
  107. }
  108. /** Returns the i'th Interval in this. */
  109. private Interval get(int i) {
  110. return list.get(i);
  111. }
  112. public int max() {
  113. if(list.isEmpty()) {
  114. return -1;
  115. } else {
  116. return list.get(list.size()-1).high;
  117. }
  118. }
  119. public int min() {
  120. if(list.isEmpty()) {
  121. return -1;
  122. } else {
  123. return list.get(0).low;
  124. }
  125. }
  126. /**
  127. * Returns the largest i s.t. list[i].low<=x, or -1 if no such i.
  128. * Note that x may or may not overlap the interval list[i].<p>
  129. *
  130. * This method uses binary search and runs in O(log N) time, where
  131. * N=list.size(). The standard Java binary search methods could not
  132. * be used because they only return exact matches. Also, they require
  133. * allocating a dummy Interval to represent x.
  134. */
  135. private int search(int x) {
  136. int low=0;
  137. int high=list.size()-1;
  138. while (low<=high) {
  139. int i=(low+high)/2;
  140. int li=get(i).low;
  141. if (li<x)
  142. low=i+1;
  143. else if (x<li)
  144. high=i-1;
  145. else
  146. return i;
  147. }
  148. //Remarkably, this does the right thing.
  149. return high;
  150. }
  151. //////////////////////// Set-like Public Methods //////////////////////////
  152. public IntSet() {
  153. this.list=new ArrayList<Interval>();
  154. }
  155. public IntSet(int expectedSize) {
  156. this.list=new ArrayList<Interval>(expectedSize);
  157. }
  158. public IntSet(IntSet toCopy) {
  159. this(toCopy.size());
  160. addAll(toCopy);
  161. }
  162. public int size() {
  163. return this.size;
  164. }
  165. public void clear() {
  166. this.size = 0;
  167. this.list.clear();
  168. }
  169. public boolean contains(int x) {
  170. int i=search(x);
  171. if (i==-1)
  172. return false;
  173. Interval li=get(i);
  174. assert li.low<=x : "Bad return value from search.";
  175. if (x<=li.high)
  176. return true;
  177. else
  178. return false;
  179. }
  180. /** @return true if the int was added, false if it already contained the int. */
  181. public boolean add(int x) {
  182. //This code is a pain--nine different return cases. It could be
  183. //factored somewhat, but I believe the following is the easiest to
  184. //understand. The cases are illustrated to the right.
  185. int i=search(x);
  186. //Optimistically increment size. Decrement it later if needed.
  187. size++;
  188. //Add x to beginning of list
  189. if (i==-1) {
  190. if ( list.size()==0 || x<(get(0).low-1) ) {
  191. //1. Add [x, x] to beginning of list. x ---
  192. list.add(0, new Interval(x));
  193. return true;
  194. } else {
  195. //2. Merge x with beginning of list. x----
  196. get(0).low=x;
  197. return true;
  198. }
  199. }
  200. Interval lower=get(i);
  201. assert(lower.low<=x);
  202. if (x<=lower.high) {
  203. //3. x already in this. --x--
  204. size--; //Undo previous increment.
  205. return false;
  206. }
  207. //Adding x to end of the list.
  208. if (i==(list.size()-1)) {
  209. if (lower.high < (x-1)) {
  210. //4. Add x to end of list --- x
  211. list.add(new Interval(x));
  212. return true;
  213. } else {
  214. //5. Merge x with end of list ----x
  215. lower.high=x;
  216. return true;
  217. }
  218. }
  219. //Adding x to middle of the list
  220. Interval higher=get(i+1);
  221. boolean touchesLower=(lower.high==(x-1));
  222. boolean touchesHigher=(x==(higher.low-1));
  223. if (touchesLower) {
  224. if (touchesHigher) {
  225. //6. Merge lower and higher intervals --x--
  226. lower.high=higher.high;
  227. list.remove(i+1);
  228. return true;
  229. } else {
  230. //7. Merge with lower interval --x --
  231. lower.high=x;
  232. return true;
  233. }
  234. } else {
  235. if (touchesHigher) {
  236. //8. Merge with higher interval -- x--
  237. higher.low=x;
  238. return true;
  239. } else {
  240. //9. Insert as new element -- x --
  241. list.add(i+1, new Interval(x));
  242. return true;
  243. }
  244. }
  245. }
  246. public boolean remove(int x) {
  247. //Find the interval overlapping x.
  248. int i=search(x);
  249. if (i==-1 || x>get(i).high)
  250. //1. x not in this. ----
  251. return false;
  252. Interval interval=get(i);
  253. boolean touchesLow=(interval.low==x);
  254. boolean touchesHigh=(interval.high==x);
  255. if (touchesLow) {
  256. if (touchesHigh) {
  257. //2. Singleton interval. Remove. -- x --
  258. list.remove(i);
  259. }
  260. else {
  261. //3. Modify low end. x---
  262. interval.low++;
  263. }
  264. } else {
  265. if (touchesHigh) {
  266. //4. Modify high end. ---x
  267. interval.high--;
  268. } else {
  269. //5. Split entire interval. --x--
  270. Interval newInterval=new Interval(x+1, interval.high);
  271. interval.high=x-1;
  272. list.add(i+1, newInterval);
  273. }
  274. }
  275. size--;
  276. return true;
  277. }
  278. public boolean addAll(IntSet s) {
  279. //TODO2: implement more efficiently!
  280. boolean ret=false;
  281. for (IntSetIterator iter=s.iterator(); iter.hasNext(); ) {
  282. ret=(ret | this.add(iter.next()));
  283. }
  284. return ret;
  285. }
  286. public boolean removeAll(IntSet s) {
  287. //TODO2: implement more efficiently!
  288. boolean ret=false;
  289. for (IntSetIterator iter=s.iterator(); iter.hasNext(); ) {
  290. ret=(ret | this.remove(iter.next()));
  291. }
  292. return ret;
  293. }
  294. public boolean retainAll(IntSet s) {
  295. //We can't modify this while iterating over it, so we need to
  296. //maintain an external list of items that must go.
  297. //TODO2: implement more efficiently!
  298. List<Integer> removeList = new ArrayList<Integer>();
  299. for (IntSetIterator iter = this.iterator(); iter.hasNext(); ) {
  300. int x = iter.next();
  301. if (! s.contains(x))
  302. removeList.add(x);
  303. }
  304. //It's marginally more efficient to remove items from end to beginning.
  305. for (int i=removeList.size()-1; i>=0; i--) {
  306. int x = removeList.get(i);
  307. this.remove(x);
  308. }
  309. //Did we remove any items?
  310. return removeList.size()>0;
  311. }
  312. /** Ensures that this consumes the minimum amount of memory. This method
  313. * should typically be called after the last call to add(..). Insertions
  314. * can still be done after the call, but they might be slower.
  315. *
  316. * Because this method only affects the performance of this, there
  317. * is no modifies clause listed. */
  318. public void trim() {
  319. list.trimToSize();
  320. }
  321. /**
  322. * Returns the values of this in order from lowest to highest, as int.
  323. * @requires this not modified while iterator in use
  324. */
  325. public IntSetIterator iterator() {
  326. return new IntSetIterator();
  327. }
  328. /** Yields a sequence of int's (not Object's) in order, without removal
  329. * support. Otherwise exactly like an Iterator. */
  330. public class IntSetIterator {
  331. /** The next interval to yield */
  332. private int i;
  333. /** The next element to yield, from the i'th interval, or undefined
  334. * if there are no more intervals to yield.
  335. * INVARIANT: i<list.size() ==> get(i).low<=next<=get(i).high */
  336. private int next;
  337. private IntSetIterator() {
  338. i=0;
  339. if (i<list.size())
  340. next=get(i).low;
  341. }
  342. public boolean hasNext() {
  343. return i<list.size();
  344. }
  345. public int next() throws NoSuchElementException {
  346. if (! hasNext())
  347. throw new NoSuchElementException();
  348. int ret=next;
  349. next++;
  350. if (next>get(i).high) {
  351. //Advance to next interval.
  352. i++;
  353. if (i<list.size())
  354. next=get(i).low;
  355. }
  356. return ret;
  357. }
  358. }
  359. @Override
  360. public String toString() {
  361. return list.toString();
  362. }
  363. }