Skip to content

Commit a7ba26b

Browse files
committed
Sorted List binarySearch functions
1 parent c2a2819 commit a7ba26b

File tree

2 files changed

+377
-8
lines changed

2 files changed

+377
-8
lines changed

src/main/java/org/apache/commons/collections4/ListUtils.java

Lines changed: 230 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,8 @@
1616
*/
1717
package org.apache.commons.collections4;
1818

19-
import java.util.AbstractList;
20-
import java.util.ArrayList;
21-
import java.util.Collection;
22-
import java.util.Collections;
23-
import java.util.HashSet;
24-
import java.util.Iterator;
25-
import java.util.List;
26-
import java.util.Objects;
19+
import java.util.*;
20+
import java.util.function.Function;
2721

2822
import org.apache.commons.collections4.bag.HashBag;
2923
import org.apache.commons.collections4.functors.DefaultEquator;
@@ -132,6 +126,221 @@ public int size() {
132126
}
133127
}
134128

129+
/**
130+
* Searches element in list sorted by key. If there are multiple elements matching, it returns first occurrence.
131+
* If the list is not sorted, the result is undefined.
132+
*
133+
* @param list
134+
* list sorted by key field
135+
* @param key
136+
* key to search for
137+
* @param keyExtractor
138+
* function to extract key from element
139+
* @param comparator
140+
* comparator for keys
141+
*
142+
* @return
143+
* index of the first occurrence of search key, if it is contained in the list; otherwise,
144+
* (-first_greater - 1). The first_greater is the index of lowest greater element in the list - if all elements
145+
* are lower, the first_greater is defined as list.size().
146+
*
147+
* @param <T>
148+
* type of list element
149+
* @param <K>
150+
* type of key
151+
*/
152+
public static <K, T> int binarySearchFirst(
153+
List<T> list,
154+
K key,
155+
Function<T, K> keyExtractor, Comparator<? super K> comparator
156+
) {
157+
return binarySearchFirst0(list, 0, list.size(), key, keyExtractor, comparator);
158+
}
159+
160+
/**
161+
* Searches element in list sorted by key, within range fromIndex (inclusive) - toIndex (exclusive). If there are
162+
* multiple elements matching, it returns first occurrence. If the list is not sorted, the result is undefined.
163+
*
164+
* @param list
165+
* list sorted by key field
166+
* @param fromIndex
167+
* start index (inclusive)
168+
* @param toIndex
169+
* end index (exclusive)
170+
* @param key
171+
* key to search for
172+
* @param keyExtractor
173+
* function to extract key from element
174+
* @param comparator
175+
* comparator for keys
176+
*
177+
* @return
178+
* index of the first occurrence of search key, if it is contained in the list within specified range;
179+
* otherwise, (-first_greater - 1). The first_greater is the index of lowest greater element in the list - if
180+
* all elements are lower, the first_greater is defined as toIndex.
181+
*
182+
* @throws ArrayIndexOutOfBoundsException
183+
* when fromIndex or toIndex is out of array range
184+
* @throws IllegalArgumentException
185+
* when fromIndex is greater than toIndex
186+
*
187+
* @param <T>
188+
* type of list element
189+
* @param <K>
190+
* type of key
191+
*/
192+
public static <T, K> int binarySearchFirst(
193+
List<T> list,
194+
int fromIndex, int toIndex,
195+
K key,
196+
Function<T, K> keyExtractor, Comparator<? super K> comparator
197+
) {
198+
checkRange(list.size(), fromIndex, toIndex);
199+
200+
return binarySearchFirst0(list, fromIndex, toIndex, key, keyExtractor, comparator);
201+
}
202+
203+
// common implementation for binarySearch methods, with same semantics:
204+
private static <T, K> int binarySearchFirst0(
205+
List<T> list,
206+
int fromIndex, int toIndex,
207+
K key,
208+
Function<T, K> keyExtractor, Comparator<? super K> comparator
209+
) {
210+
int l = fromIndex;
211+
int h = toIndex - 1;
212+
213+
while (l <= h) {
214+
final int m = (l + h) >>> 1; // unsigned shift to avoid overflow
215+
final K value = keyExtractor.apply(list.get(m));
216+
final int c = comparator.compare(value, key);
217+
if (c < 0) {
218+
l = m + 1;
219+
} else if (c > 0) {
220+
h = m - 1;
221+
} else if (l < h) {
222+
// possibly multiple matching items remaining:
223+
h = m;
224+
} else {
225+
// single matching item remaining:
226+
return m;
227+
}
228+
}
229+
230+
// not found, the l points to the lowest higher match:
231+
return -l - 1;
232+
}
233+
234+
/**
235+
* Searches element in list sorted by key. If there are multiple elements matching, it returns last occurrence.
236+
* If the list is not sorted, the result is undefined.
237+
*
238+
* @param list
239+
* list sorted by key field
240+
* @param key
241+
* key to search for
242+
* @param keyExtractor
243+
* function to extract key from element
244+
* @param comparator
245+
* comparator for keys
246+
*
247+
* @return
248+
* index of the last occurrence of search key, if it is contained in the list; otherwise,
249+
* (-first_greater - 1). The first_greater is the index of lowest greater element in the list - if all elements
250+
* are lower, the first_greater is defined as list.size() .
251+
*
252+
* @param <T>
253+
* type of array element
254+
* @param <K>
255+
* type of key
256+
*/
257+
public static <K, T> int binarySearchLast(
258+
List<T> list,
259+
K key,
260+
Function<T, K> keyExtractor, Comparator<? super K> comparator
261+
) {
262+
return binarySearchLast0(list, 0, list.size(), key, keyExtractor, comparator);
263+
}
264+
265+
/**
266+
* Searches element in list sorted by key, within range fromIndex (inclusive) - toIndex (exclusive). If there are
267+
* multiple elements matching, it returns last occurrence. If the list is not sorted, the result is undefined.
268+
*
269+
* @param list
270+
* list sorted by key field
271+
* @param fromIndex
272+
* start index (inclusive)
273+
* @param toIndex
274+
* end index (exclusive)
275+
* @param key
276+
* key to search for
277+
* @param keyExtractor
278+
* function to extract key from element
279+
* @param comparator
280+
* comparator for keys
281+
*
282+
* @return
283+
* index of the last occurrence of search key, if it is contained in the list within specified range;
284+
* otherwise, (-first_greater - 1). The first_greater is the index of lowest greater element in the list - if
285+
* all elements are lower, the first_greater is defined as toIndex.
286+
*
287+
* @throws ArrayIndexOutOfBoundsException
288+
* when fromIndex or toIndex is out of array range
289+
* @throws IllegalArgumentException
290+
* when fromIndex is greater than toIndex
291+
*
292+
* @param <T>
293+
* type of array element
294+
* @param <K>
295+
* type of key
296+
*/
297+
public static <T, K> int binarySearchLast(
298+
List<T> list,
299+
int fromIndex, int toIndex,
300+
K key,
301+
Function<T, K> keyExtractor, Comparator<? super K> comparator
302+
) {
303+
checkRange(list.size(), fromIndex, toIndex);
304+
305+
return binarySearchLast0(list, fromIndex, toIndex, key, keyExtractor, comparator);
306+
}
307+
308+
// common implementation for binarySearch methods, with same semantics:
309+
private static <T, K> int binarySearchLast0(
310+
List<T> list,
311+
int fromIndex, int toIndex,
312+
K key,
313+
Function<T, K> keyExtractor, Comparator<? super K> comparator
314+
) {
315+
int l = fromIndex;
316+
int h = toIndex - 1;
317+
318+
while (l <= h) {
319+
final int m = (l + h) >>> 1; // unsigned shift to avoid overflow
320+
final K value = keyExtractor.apply(list.get(m));
321+
final int c = comparator.compare(value, key);
322+
if (c < 0) {
323+
l = m + 1;
324+
} else if (c > 0) {
325+
h = m - 1;
326+
} else if (m + 1 < h) {
327+
// matching, more than two items remaining:
328+
l = m;
329+
} else if (m + 1 == h) {
330+
// two items remaining, next loops would result in unchanged l and h, we have to choose m or h:
331+
final K valueH = keyExtractor.apply(list.get(h));
332+
final int cH = comparator.compare(valueH, key);
333+
return cH == 0 ? h : m;
334+
} else {
335+
// one item remaining, single match:
336+
return m;
337+
}
338+
}
339+
340+
// not found, the l points to the lowest higher match:
341+
return -l - 1;
342+
}
343+
135344
/**
136345
* Returns either the passed in list, or if the list is {@code null},
137346
* the value of {@code defaultList}.
@@ -741,6 +950,19 @@ public static <E> List<E> unmodifiableList(final List<? extends E> list) {
741950
return UnmodifiableList.unmodifiableList(list);
742951
}
743952

953+
static void checkRange(int length, int fromIndex, int toIndex) {
954+
if (fromIndex > toIndex) {
955+
throw new IllegalArgumentException(
956+
"fromIndex(" + fromIndex + ") > toIndex(" + toIndex + ")");
957+
}
958+
if (fromIndex < 0) {
959+
throw new ArrayIndexOutOfBoundsException(fromIndex);
960+
}
961+
if (toIndex > length) {
962+
throw new ArrayIndexOutOfBoundsException(toIndex);
963+
}
964+
}
965+
744966
/**
745967
* Don't allow instances.
746968
*/

0 commit comments

Comments
 (0)