Java-сборник binarySearch не работает должным образом

Я просто пытаюсь использовать собственный Java-бинарный поиск, надеясь, что он всегда найдет первое вхождение. Но это не всегда возвращает первое вхождение, что я сделал здесь неправильно?

import java.util.*;

class BinarySearchWithComparator
{
  public static void main(String[] args)
  {
    // Please scroll down to see 'User' class implementation.
    List<User> l = new ArrayList<User>();
    l.add(new User(10, "A"));
    l.add(new User(10, "A"));
    l.add(new User(10, "A"));

    l.add(new User(20, "B"));
    l.add(new User(20, "B"));
    l.add(new User(20, "B"));
    l.add(new User(20, "B"));
    l.add(new User(20, "B"));
    l.add(new User(20, "B"));
    l.add(new User(20, "B"));


    l.add(new User(30, "C"));
    l.add(new User(30, "C"));
    l.add(new User(30, "C"));
    l.add(new User(30, "C"));

    Comparator<User> c = new Comparator<User>() {
      public int compare(User u1, User u2) {
        return u1.getId().compareTo(u2.getId());
      }
    };

    // Must pass in an object of type 'User' as the key.
    // The key is an 'User' with the 'id' which is been searched for.
    // The 'name' field is not used in the comparison for the binary search,
    // so it can be a dummy value -- here it is omitted with a null.
    //
    // Also note that the List must be sorted before running binarySearch,
    // in this case, the list is already sorted.
    Collections.sort(l,c);

    int index = Collections.binarySearch(l, new User(10, null), c);
    System.out.println(index);

    index = Collections.binarySearch(l, new User(20, null), c);
    System.out.println(index);

    index = Collections.binarySearch(l, new User(30, null), c);
    System.out.println(index);

    index = Collections.binarySearch(l, new User(42, null), c);
    System.out.println(index);
  }
}

class User {
  private int id;
  private String name;

  public User(int id, String name) {
    this.id = id;
    this.name = name;
  }

  public Integer getId() {
    return Integer.valueOf(id);
  }
}

========

Result:
2 //not 0 ?
6 //not 3?
10 //ok
-15  ok

Why first 10 is not index 0 ?
Why first 20 is not index 3 ?
OK , 30 first occurrence is index 10

======================================

Update

Теперь кажется, что API не так уж и garante! Может ли кто-нибудь дать мне рабочий пример того, как найти первое вхождение и последнее вхождение для данного элемента (например, User (10, null)?

Большое спасибо.

Ответ 1

Java не дает гарантии относительно того, какой элемент среди равных будет возвращен. Когда у вас есть несколько элементов в равном диапазоне, вам нужно пройти список вниз, чтобы найти первый элемент, который соответствует тому, что вы ищете, например:

User lookFor = new User(30, null);
index = Collections.binarySearch(l, lookFor, c);
while (index > 0 && c.compare(lookFor, l[index-1]) == 0) {
    index--;
}
// At this point the index is at the first element of the equal range

Вы можете обернуть эту логику в статической функции, чтобы избежать записи явного цикла каждый раз.

Обратите внимание, что если ваша коллекция является произвольным доступом, это приведет к наихудшему результату (когда есть много одинаковых элементов) для деградации от O (lg (n)) до O (n).

Ответ 2

но у вас есть более одного элемента с идентификатором 10 в вашем списке. Таким образом, binarySearch не ошибочен

Руководство для Java для бинарного поиска говорит следующее:

Ищет указанный список для указанного объекта с использованием алгоритма бинарного поиска. Список должен быть отсортирован в порядке возрастания в соответствии с естественным упорядочением его элементов (как по методу сортировки (списка)) до совершения этого вызова. Если он не отсортирован, результаты undefined. Если список содержит несколько элементов, равных указанному объекту, нет никакой гарантии, какой из них будет найден.

http://docs.oracle.com/javase/6/docs/api/java/util/Collections.html#binarySearch(java.util.List, T)

Ответ 3

из javadoc в Array.binarySearch():

Searches a range of the specified array of bytes for the specified value using the
binary search algorithm. The range must be sorted (as by the sort(byte[], int, int) 
method) prior to making this call. If it is not sorted, the results are undefined. 
If the range contains multiple elements with the specified value, there is no 
guarantee which one will be found.

Ответ 4

Многие из элементов в вашем списке равны для сортировки. Если два элемента имеют одинаковый идентификатор, то на самом деле не имеет значения, какой из них вы используете, с точки зрения сортировки. Collections.binarySearch просто разбивает список пополам, пока не найдет совпадение. Как только он найдет совпадение, он не будет проверять предыдущий элемент, чтобы увидеть, совпадает ли он и с ним, он просто вернет индекс.

Рассмотрим более надежную реализацию compareTo, которая сортируется не только с идентификатором, если элементы будут иметь идентификаторы.