View Javadoc

1   package org.varienaja.util;
2   
3   import java.util.Iterator;
4   import java.util.Set;
5   import java.util.TreeSet;
6   
7   /**
8    * A Range contains a set of long-values between a start- and an end-value. 
9    * The values in between are assumed to be there, but they are not stored
10   * in memory.
11   * @author Arjan Verstoep
12   *
13   */
14  class Range implements Comparable<Range> {
15  	//TODO Checking for start<=end etc.
16  	private long _start, _end;
17  	
18  	public Range(long start, long end) {
19  		setStart(start);
20  		setEnd(end);
21  	}
22  
23  	public long getEnd() {
24  		return _end;
25  	}
26  
27  	public void setEnd(long end) {
28  		if (end>=_start) _end = end;
29  	}
30  
31  	public long getStart() {
32  		return _start;
33  	}
34  
35  	public void setStart(long start) {
36  		_start = start;
37  	}
38  	
39  	public boolean contains(long l) {
40  		return (_start<=l) && (l<=_end);
41  	}
42  	
43  	public boolean equals(Object o) {
44  		if (o instanceof Range) {
45  			Range test = (Range) o;
46  			return (test.getEnd()==getEnd()) && (test.getStart()==getStart());
47  		} else {
48  			return false;
49  		}
50  	}
51  	
52  	public int hashCode() {
53  		Long l = _start * _end;
54  		return l.intValue();
55  	}
56  	
57  	public int compareTo(Range test) {
58  		if (test.getStart()>getEnd()) {
59  			return -1;
60  		} else {
61  			if (test.getEnd()<getStart()) {
62  				return 1;
63  			} else {
64  				return 0;
65  			}
66  		}
67  	}
68  	
69  	public long itemCount() {
70  		return 1+_end-_start;
71  	}
72  	
73  	public String toString() {
74  		return "["+_start+"..."+_end+"]";
75  	}
76  	
77  }
78  
79  /**
80   * A Dense Set is a set of long-values. The Set is meant to contain many consecutive longs
81   * while using as little memory as possible. The more holes there are in the list of
82   * long-values, the more memory will be consumed. The memory consumption per hole is
83   * constant and independent of the size of the hole.
84   * 
85   * The advantage of a DenseSet over a BitSet, is that the DenseSet has a
86   * constant size. It can hold MAX_LONG values, and still use two longs memory.
87   * Only the holes consume memory. A Bitset containing MAX_LONG bits would take
88   * an insane amount of 2^64/8 bytes, regardless of the amount of holes. So, when
89   * the amount of holes stays low enough, the DenseSet is a better choise than a
90   * BitSet.
91   * 
92   * @author varienaja
93   */
94  public class DenseSet { //TODO implements Set, SortedSet, Collection, etc...
95  	protected Set<Range> _ranges;
96  	
97  	public DenseSet() {
98  		_ranges = new TreeSet<Range>();
99  	}
100 	
101 	/**
102 	 * @return The amount of long-values that is contained in the Set
103 	 */
104 	public int size() {
105 		int result = 0;
106 		for (Range range:_ranges) {
107 			result += range.itemCount();
108 		}
109 		return result;
110 	}
111 	
112 	/**
113 	 * Adds a long to the Set of longs.
114 	 * @param l The value to be added.
115 	 * @return Whether the Set changed because of this operation
116 	 */
117 	public boolean add(long l) {
118 		Range candidate = findRangeContaining(l);
119 		if (candidate==null) {
120 			//We've got work to do. Maybe l can be put in a range just besides it..
121 			Range candidateSmaller = findRangeContaining(l-1);
122 			Range candidateLarger = findRangeContaining(l+1);
123 			if (candidateSmaller==null) {
124 				if (candidateLarger==null) {
125 					_ranges.add(new Range(l,l));
126 				} else {
127 					candidateLarger.setStart(l);
128 				}
129 			} else {
130 				if (candidateLarger==null) {
131 					candidateSmaller.setEnd(l);
132 				} else {
133 					//Merge candidateLarger and candidateSmaller
134 					_ranges.remove(candidateLarger);
135 					candidateSmaller.setEnd(candidateLarger.getEnd());
136 				}
137 			}
138 			return true;
139 		} else {
140 			return false; //Element is already contained in this Set
141 		}
142 	}
143 	
144 	/**
145 	/**
146 	 * Removes a long value from the Set of longs.
147 	 * @param l The long-value to be removed.
148 	 * @return Whether the long was removed
149 	 */
150 	public boolean remove(long l) {
151 		Range range = findRangeContaining(l);
152 		if (range!=null) {
153 			if (range.getStart()==l) {
154 				//Make range smaller
155 				if (range.getEnd()>l) {
156 					range.setStart(l+1);
157 				} else {
158 					_ranges.remove(range);
159 				}
160 			} else {
161 				//Make range smaller
162 				if (range.getEnd()==l) {
163 					if (range.getStart()<l) {
164 						range.setEnd(l-1);
165 					} else {
166 						_ranges.remove(range);
167 					}
168 				} else {
169 					//Split range
170 					Range newOne = new Range(l+1,range.getEnd()); 
171 					range.setEnd(l-1);
172 					_ranges.add(newOne);
173 				}
174 			}
175 			return true;
176 		} else {
177 			return false;
178 		}
179 	}
180 	
181 	/**
182 	 * Removes all Long-values from the Set
183 	 *
184 	 */
185 	public void clear() {
186 		_ranges.clear();
187 	}
188 	
189 	/**
190 	 * Find the Range-object that contains a long-value
191 	 * @param l The long-value that we're looking for
192 	 * @return The Range that contains the long-value, or null if there is no such Range 
193 	 */
194 	private Range findRangeContaining(long l) {
195 		//TODO Create Range, use that one to binary-search the TreeSet
196 		for (Range range:_ranges) {
197 			if (range.contains(l)) return range;
198 		}
199 		return null;
200 	}
201 	
202 	public String toString() {
203 		StringBuilder sb = new StringBuilder();
204 		sb.append("Size: ");
205 		sb.append(size());
206 		sb.append(" Elements: ");
207 		sb.append(_ranges.size());
208 		sb.append(" ");
209 		Iterator<Range> it = _ranges.iterator();
210 		while (it.hasNext()) {
211 			sb.append(it.next().toString());
212 			if (it.hasNext()) sb.append(", ");
213 		}
214 		return sb.toString();
215 	}
216 	
217 	/**
218 	 * Returns a string representation of the contents of the set in
219 	 * the format: a-b,c,d-e, ...
220 	 * @return A String representation of the contents of the dense set.
221 	 */
222 	public String prettyPrint() {
223 		StringBuilder sb = new StringBuilder();
224 		Iterator<Range> it = _ranges.iterator();
225 		while(it.hasNext()) {
226 			Range range = it.next();
227 			long start = range.getStart();
228 			long end = range.getEnd();
229 			sb.append(start);
230 			if(start!=end) {
231 				sb.append("-");
232 				sb.append(range.getEnd());
233 			}
234 			if(it.hasNext()) {
235 				sb.append(",");
236 			}
237 		}
238 		return sb.toString();
239 	}
240 	
241 	/**
242 	 * Searches for a long-value at a specific position.
243 	 * @param index The position to look in
244 	 * @return returns the long that is at position index in the Set
245 	 */
246 	public long get(long index) {
247 		for (Range range:_ranges) {
248 			index -= range.itemCount();
249 			if (index<0) {
250 				return range.getStart() + index + range.itemCount();
251 			}
252 		}
253 		return -1; //TODO ArrayOutOfBoundsException or something alike
254 	}
255 	
256 	/**
257 	 * Checks whether a certain value exists in the Set.
258 	 * @param l The value to check for.
259 	 * @return True, if this value is contained in this Set, False otherwise.
260 	 */
261 	public boolean contains(long l) {
262 		return findRangeContaining(l)!=null;
263 	}
264 
265 	public boolean equals(Object o) {
266 		boolean result = false;
267 		if (o instanceof DenseSet) {
268 			DenseSet other = (DenseSet) o;
269 			if (this.size()==other.size()) {
270 				result = true;
271 				Iterator<Range> it1 = _ranges.iterator();
272 				Iterator<Range> it2 = other._ranges.iterator();
273 				while(it1.hasNext()) {
274 					Range r1 = it1.next();
275 					if(it2.hasNext()) {
276 						Range r2 = it2.next();
277 						if(!r1.equals(r2)) {
278 							break;
279 						}
280 					} else {
281 						break;
282 					}
283 				}
284 			}
285 		}
286 		return result;
287 	}
288 	
289 	public int hashCode() {
290 		int result = 0;
291 		for(Range range: _ranges) {
292 			result = (3 * result) + range.hashCode();
293 		}
294 		return result;
295 	}
296 }