简体   繁体   中英

How to use binarySearch in Collections?

l'm have class Animal with field: weight and color. How I can used Collections.binarySearch in this case (use binary search to find some animal by required size):

 public static int searchElement(final List<? extends Animal> list, final int weight) {
    return Collections.binarySearch(list, weight...);
}

Unfortunately, it is not directly possible to search for an element based on a certain property, using the built-in functions.

There are at least three options how this could be solved:

  • Creating a "template" with the desired property, and search for this
  • Extract the property values into an array, and search in this array
  • Create an own, property-based binary search

The first one may not be applicable in all cases, and looks questionable in some ways.

The second one is rather easy and could be a viable option. But assuming that you are doing a binary search because the collection is large , this may impose some overhead in terms of memory and performance.

The third option is probably the most elegant and versatile one. Fortunately, the binarySearch itself is not so complex - only a few lines of code - so it's easy to craft an own one that receives some "key extracting Function ".

I have sketched these approaches in the following example:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;

class Animal implements Comparable<Animal>
{
    private final int weight;

    Animal(int weight)
    {
        this.weight = weight;
    }

    public int getWeight()
    {
        return weight;
    }

    @Override
    public int compareTo(Animal that)
    {
        return Integer.compare(this.weight, that.weight);
    }
}

public class CollectionBinarySearch
{
    public static void main(String[] args)
    {
        List<Animal> animals = new ArrayList<Animal>();
        animals.add(new Animal(10));
        animals.add(new Animal(40));
        animals.add(new Animal(20));
        animals.add(new Animal(90));
        animals.add(new Animal(290));
        animals.add(new Animal(130));

        Collections.sort(animals);

        System.out.println(searchWithInstance(animals, 90));
        System.out.println(searchWithInstance(animals, 50));

        System.out.println(searchWithArray(animals, 90));
        System.out.println(searchWithArray(animals, 50));

        System.out.println(searchWithFunction(animals, Animal::getWeight, 90));
        System.out.println(searchWithFunction(animals, Animal::getWeight, 50));

    }

    public static int searchWithInstance(
        final List<? extends Animal> list, final int weight) {
        return Collections.binarySearch(list, new Animal(weight));
    }

    public static int searchWithArray(
        final List<? extends Animal> list, final int weight) {
        int[] array = list.stream().mapToInt(Animal::getWeight).toArray();
        return Arrays.binarySearch(array, weight);
    }        

    // Adapted from Collections#binarySearch
    private static <T, K extends Comparable<? super K>> int searchWithFunction(
        List<? extends T> list, Function<? super T, K> keyExtractor, K key) {
        int low = 0;
        int high = list.size()-1;
        while (low <= high) {
            int mid = (low + high) >>> 1;
            T midVal = list.get(mid);
            int cmp = keyExtractor.apply(midVal).compareTo(key);
            if (cmp < 0)
                low = mid + 1;
            else if (cmp > 0)
                high = mid - 1;
            else
                return mid; // key found
        }
        return -(low + 1);  // key not found
    }

}

You can lazily transform the list into a list of the type you want:

class LazyTransform extends AbstractList<Integer> implements RandomAccess {
    @Override public Integer get(int index) { return items.get(index).weight(); }
    @Override public int size() { return items.size(); }
}
Collections.binarySearch(new LazyTransform(), searchWeight);

The transform is lazy, in that it will only convert the values that are being compared.


Or, if you can use Guava's Lists.transform :

Collections.binarySearch(Lists.transform(animals, Animal::weight), searchWeight);

And yes, if the input list is RandomAccess , so is the transformed list.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM