-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
binarySearch on sorted arrays and sorted lists
- Loading branch information
Showing
4 changed files
with
412 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
110 changes: 110 additions & 0 deletions
110
src/main/java/org/apache/commons/lang3/SortedListUtils.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
/* | ||
* Licensed to the Apache Software Foundation (ASF) under one or more | ||
* contributor license agreements. See the NOTICE file distributed with | ||
* this work for additional information regarding copyright ownership. | ||
* The ASF licenses this file to You under the Apache License, Version 2.0 | ||
* (the "License"); you may not use this file except in compliance with | ||
* the License. You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package org.apache.commons.lang3; | ||
|
||
import java.util.Comparator; | ||
import java.util.List; | ||
import java.util.function.Function; | ||
|
||
|
||
/** | ||
* Operations on sorted {@link List}. | ||
*/ | ||
public class SortedListUtils { | ||
/** | ||
* Finds element in sorted list. | ||
* | ||
* @param list | ||
* list sorted by key field | ||
* @param key | ||
* key to search for | ||
* @param keyExtractor | ||
* function to extract key from element | ||
* @param comparator | ||
* comparator for keys | ||
* | ||
* @return | ||
* index of the search key, if it is contained in the list within specified range; otherwise, | ||
* (-first_greater - 1). The first_greater is the index of lowest greater element in the list - if all elements | ||
* are lower, the first_greater is defined as toIndex. | ||
* | ||
* @param <T> | ||
* type of list element | ||
* @param <K> | ||
* type of key | ||
*/ | ||
public static <K, T> int binarySearch( | ||
List<T> list, | ||
K key, | ||
Function<T, K> keyExtractor, Comparator<? super K> comparator | ||
) { | ||
return binarySearch(list, 0, list.size(), key, keyExtractor, comparator); | ||
} | ||
|
||
/** | ||
* Finds element in sorted list, within range fromIndex - toIndex (inclusive - exclusive). | ||
* | ||
* @param list | ||
* list sorted by key field | ||
* @param fromIndex | ||
* start index | ||
* @param toIndex | ||
* end index (exclusive) | ||
* @param key | ||
* key to search for | ||
* @param keyExtractor | ||
* function to extract key from element | ||
* @param comparator | ||
* comparator for keys | ||
* | ||
* @return | ||
* index of the search key, if it is contained in the list within specified range; otherwise, | ||
* (-first_greater - 1). The first_greater is the index of lowest greater element in the list - if all elements | ||
* are lower, the first_greater is defined as toIndex. | ||
* | ||
* @param <T> | ||
* type of array element | ||
* @param <K> | ||
* type of key | ||
*/ | ||
public static <T, K> int binarySearch( | ||
List<T> list, | ||
int fromIndex, int toIndex, | ||
K key, | ||
Function<T, K> keyExtractor, Comparator<? super K> comparator | ||
) { | ||
int l = fromIndex; | ||
int h = toIndex - 1; | ||
|
||
while (l <= h) { | ||
int m = (l + h) >>> 1; // unsigned shift to avoid overflow | ||
K value = keyExtractor.apply(list.get(m)); | ||
int c = comparator.compare(value, key); | ||
if (c < 0) { | ||
l = m + 1; | ||
} else if (c > 0) { | ||
h = m - 1; | ||
} else { | ||
// 0, found | ||
return m; | ||
} | ||
} | ||
|
||
// not found, the l points to the lowest higher match: | ||
return -l - 1; | ||
} | ||
} |
109 changes: 109 additions & 0 deletions
109
src/test/java/org/apache/commons/lang3/ArrayUtilsBinarySearchTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
/* | ||
* Licensed to the Apache Software Foundation (ASF) under one or more | ||
* contributor license agreements. See the NOTICE file distributed with | ||
* this work for additional information regarding copyright ownership. | ||
* The ASF licenses this file to You under the Apache License, Version 2.0 | ||
* (the "License"); you may not use this file except in compliance with | ||
* the License. You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package org.apache.commons.lang3; | ||
|
||
import static org.junit.jupiter.api.Assertions.assertEquals; | ||
import static org.junit.jupiter.api.Assertions.assertThrowsExactly; | ||
|
||
import java.util.stream.IntStream; | ||
|
||
import org.junit.jupiter.api.Test; | ||
import org.junit.jupiter.api.Timeout; | ||
|
||
/** | ||
* Unit tests {@link ArrayUtils} binarySearch functions. | ||
*/ | ||
public class ArrayUtilsBinarySearchTest extends AbstractLangTest { | ||
|
||
@Test | ||
public void binarySearch_whenLowHigherThanEnd_throw() { | ||
final Data[] list = createList(0, 1); | ||
assertThrowsExactly(IllegalArgumentException.class, () -> ArrayUtils.binarySearch(list, 1, 0, 0, Data::getValue, Integer::compare)); | ||
} | ||
|
||
@Test | ||
public void binarySearch_whenLowNegative_throw() { | ||
final Data[] list = createList(0, 1); | ||
assertThrowsExactly(ArrayIndexOutOfBoundsException.class, () -> ArrayUtils.binarySearch(list, -1, 0, 0, Data::getValue, Integer::compare)); | ||
} | ||
|
||
@Test | ||
public void binarySearch_whenEndBeyondLength_throw() { | ||
final Data[] list = createList(0, 1); | ||
assertThrowsExactly(ArrayIndexOutOfBoundsException.class, () -> ArrayUtils.binarySearch(list, 0, 3, 0, Data::getValue, Integer::compare)); | ||
} | ||
|
||
@Test | ||
public void binarySearch_whenEmpty_returnM1() { | ||
final Data[] list = createList(); | ||
final int found = ArrayUtils.binarySearch(list, 0, Data::getValue, Integer::compare); | ||
assertEquals(-1, found); | ||
} | ||
|
||
@Test | ||
public void binarySearch_whenExists_returnIndex() { | ||
final Data[] list = createList(0, 1, 2, 4, 7, 9, 12, 15, 17, 19, 25); | ||
final int found = ArrayUtils.binarySearch(list, 9, Data::getValue, Integer::compare); | ||
assertEquals(5, found); | ||
} | ||
|
||
@Test | ||
public void binarySearch_whenNotExists_returnMinusInsertion() { | ||
final Data[] list = createList(0, 1, 2, 4, 7, 9, 12, 15, 17, 19, 25); | ||
final int found = ArrayUtils.binarySearch(list, 8, Data::getValue, Integer::compare); | ||
assertEquals(-6, found); | ||
} | ||
|
||
@Test | ||
public void binarySearch_whenNotExistsBeginning_returnMinus1() { | ||
final Data[] list = createList(0, 1, 2, 4, 7, 9, 12, 15, 17, 19, 25); | ||
final int found = ArrayUtils.binarySearch(list, -3, Data::getValue, Integer::compare); | ||
assertEquals(-1, found); | ||
} | ||
|
||
@Test | ||
public void binarySearch_whenNotExistsEnd_returnMinusLength() { | ||
final Data[] list = createList(0, 1, 2, 4, 7, 9, 12, 15, 17, 19, 25); | ||
final int found = ArrayUtils.binarySearch(list, 29, Data::getValue, Integer::compare); | ||
assertEquals(-(list.length + 1), found); | ||
} | ||
|
||
@Test | ||
@Timeout(10) | ||
public void binarySearch_whenUnsorted_dontInfiniteLoop() { | ||
final Data[] list = createList(7, 1, 4, 9, 11, 8); | ||
final int found = ArrayUtils.binarySearch(list, 10, Data::getValue, Integer::compare); | ||
} | ||
|
||
private Data[] createList(int... values) { | ||
return IntStream.of(values).mapToObj(Data::new) | ||
.toArray(Data[]::new); | ||
} | ||
|
||
static class Data { | ||
|
||
private final int value; | ||
|
||
Data(int value) { | ||
this.value = value; | ||
} | ||
|
||
public int getValue() { | ||
return value; | ||
} | ||
} | ||
} |
Oops, something went wrong.