Java эквивалентен пополам в Python
Есть ли в Java эквивалент для модуля Pyect bisect? С делением Python вы можете делить пополам массив с указаниями. Например bisect.bisect_left
делает:
Найдите правильную точку вставки для элемента в списке, чтобы сохранить отсортированный порядок. Параметры lo и hi могут использоваться для указания подмножества списка, которое следует учитывать; по умолчанию используется весь список.
Я знаю, что могу сделать это вручную и с помощью бинарного поиска, но мне было интересно, есть ли уже библиотека или коллекция, делающая это.
7 ответов
У вас есть два варианта:
java.util.Arrays.binarySearch
на массивах- (с различными перегрузками для разных типов массивов)
java.util.Collections.binarySearch
наList
- (с
Comparable
а такжеComparator
Перегрузки). - В сочетании с
List.subList(int fromIndex, int toIndex)
искать часть списка
- (с
На сегодняшний день (Java 8) этого по-прежнему нет, поэтому вы все равно должны сделать свой собственный. Вот мой:
public static int bisect_right(int[] A, int x) {
return bisect_right(A, x, 0, A.length);
}
public static int bisect_right(int[] A, int x, int lo, int hi) {
int N = A.length;
if (N == 0) {
return 0;
}
if (x < A[lo]) {
return lo;
}
if (x > A[hi - 1]) {
return hi;
}
for (;;) {
if (lo + 1 == hi) {
return lo + 1;
}
int mi = (hi + lo) / 2;
if (x < A[mi]) {
hi = mi;
} else {
lo = mi;
}
}
}
public static int bisect_left(int[] A, int x) {
return bisect_left(A, x, 0, A.length);
}
public static int bisect_left(int[] A, int x, int lo, int hi) {
int N = A.length;
if (N == 0) {
return 0;
}
if (x < A[lo]) {
return lo;
}
if (x > A[hi - 1]) {
return hi;
}
for (;;) {
if (lo + 1 == hi) {
return x == A[lo] ? lo : (lo + 1);
}
int mi = (hi + lo) / 2;
if (x <= A[mi]) {
hi = mi;
} else {
lo = mi;
}
}
}
Протестировано с (X является классом, где я храню статические методы, которые я намерен использовать повторно):
@Test
public void bisect_right() {
System.out.println("bisect_rienter code hereght");
int[] A = new int[]{0, 1, 2, 2, 2, 2, 3, 3, 5, 6};
assertEquals(0, X.bisect_right(A, -1));
assertEquals(1, X.bisect_right(A, 0));
assertEquals(6, X.bisect_right(A, 2));
assertEquals(8, X.bisect_right(A, 3));
assertEquals(8, X.bisect_right(A, 4));
assertEquals(9, X.bisect_right(A, 5));
assertEquals(10, X.bisect_right(A, 6));
assertEquals(10, X.bisect_right(A, 7));
}
@Test
public void bisect_left() {
System.out.println("bisect_left");
int[] A = new int[]{0, 1, 2, 2, 2, 2, 3, 3, 5, 6};
assertEquals(0, X.bisect_left(A, -1));
assertEquals(0, X.bisect_left(A, 0));
assertEquals(2, X.bisect_left(A, 2));
assertEquals(6, X.bisect_left(A, 3));
assertEquals(8, X.bisect_left(A, 4));
assertEquals(8, X.bisect_left(A, 5));
assertEquals(9, X.bisect_left(A, 6));
assertEquals(10, X.bisect_left(A, 7));
}
Просто для полноты, вот небольшая Java-функция, которая превращает вывод из Arrays.binarySearch
в нечто близкое к выходу из bisect_left
, Я очевидно скучаю по вещам, но это делает работу для простого случая.
public static int bisectLeft(double[] a, double key) {
int idx = Math.min(a.length, Math.abs(Arrays.binarySearch(a, key)));
while (idx > 0 && a[idx - 1] >= key) idx--;
return idx;
}
Почему бы не сделать быстрый порт самого проверенного и проверенного кода Python? Например, вот порт Java для bisect_right
:
public static int bisect_right(double[] A, double x) {
return bisect_right(A, x, 0, A.length);
}
private static int bisect_right(double[] A, double x, int lo, int hi) {
while (lo < hi) {
int mid = (lo+hi)/2;
if (x < A[mid]) hi = mid;
else lo = mid+1;
}
return lo;
}
На основе
java.util.Arrays.binarySearch
документация
Здесь я использую пример для
long[]
массив, но можно адаптировать код для использования любого из поддерживаемых типов.
int bisectRight(long[] arr, long key) {
int index = Arrays.binarySearch(arr, key);
return Math.abs(index + 1);
}
Примечание. Ограничение API Java следующим предложением из javadoc:
If the array contains multiple elements with the specified value,
there is no guarantee which one will be found
Действительно, я проверил это с отсортированным массивом отдельных элементов. Мой вариант использования был для группировки диапазонов, где
arr
массив различных временных меток, указывающих время начала интервала.
Полученный из ответа @Profiterole, вот обобщенный вариант, который работает с логической функцией int-> вместо массива. Он находит первый индекс, в котором изменяется предикат.
public class Bisect {
/**
* Look for the last index i in [min, max] such that f(i) is false.
*
* @param function monotonous function going from false to true in the [min, max] interval
*/
public static int bisectLeft(Function<Integer, Boolean> function, int min, int max) {
if (max == min) {
return max;
}
if (function.apply(min)) {
return min;
}
if (!function.apply(max)) {
return max;
}
while (true) {
if (min + 1 == max) {
return min;
}
int middle = (max + min) / 2;
if (function.apply(middle)) {
max = middle;
} else {
min = middle;
}
}
}
/**
* Look for the first index i in [min, max] such that f(i) is true.
*
* @param function monotonous function going from false to true in the [min, max] interval
*/
public static int bisectRight(Function<Integer, Boolean> function, int min, int max) {
if (max == min) {
return max;
}
if (function.apply(min)) {
return min;
}
if (!function.apply(max)) {
return max;
}
while (true) {
if (min + 1 == max) {
return max;
}
int middle = (max + min) / 2;
if (function.apply(middle)) {
max = middle;
} else {
min = middle;
}
}
}
}
Например, чтобы найти точку вставки в массиве, функция сравнивает вставленное значение со значениями массива:
@Test
public void bisect_right() {
int[] A = new int[]{0, 1, 2, 2, 2, 2, 3, 3, 5, 6};
assertEquals(0, bisectRight(f(A, -1), 0, A.length));
assertEquals(1, bisectRight(f(A, 0), 0, A.length));
assertEquals(6, bisectRight(f(A, 2), 0, A.length));
assertEquals(8, bisectRight(f(A, 3), 0, A.length));
assertEquals(8, bisectRight(f(A, 4), 0, A.length));
assertEquals(9, bisectRight(f(A, 5), 0, A.length));
assertEquals(10, bisectRight(f(A, 6), 0, A.length));
assertEquals(10, bisectRight(f(A, 7), 0, A.length));
}
public Function<Integer, Boolean> f(int[] A, int x) {
return n -> (n >= A.length || A[n] > x);
}
Вам нужно определить самостоятельно, вот мой:
bisect.bisect_left
public static int bisectLeft(int[] nums, int target) {
int i = 0;
int j = nums.length - 1;
while (i <= j) {
int m = i + (j-i) / 2;
if (nums[m] >= target) {
j = m - 1;
} else {
i = m + 1;
}
}
return i;
}
bisect.bisect_right
public static int bisectRight(int[] nums, int target) {
int i = 0;
int j = nums.length - 1;
while (i <= j) {
int m = i + (j-i) / 2;
if (nums[m] <= target) {
i = m + 1;
} else {
j = m - 1;
}
}
return j+1;
}