Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to binary-search on one field of a List's elements

Let C be a class defined (partially) by

private static class C {
    private final int x;
    // lots more fields be here

    public C(int x, /* lots more arguments here */) {
        this.x = x;
        // lots more fields initialized here
    }

    public int getX(){
        return x;
    }
}

and let cs be a List<C> implementing RandomAccess, and sorted on C.getX().

What would be the standard approach to performing a binary search in cs for x1 on C.getX()? (In other words, pretending that each element c is replaced by c.getX() and then we search for x1 among those integers.)


Collections.binarySearch(
    cs,
    new C(x1,/* dummy arguments */),
    (a,b) -> Integer.compare(a.getX(),b.getX())
);

has the drawback that it requires construction of a new C (which may require lots of dummy arguments and knowledge about C).

Collections.binarySearch(
    cs.stream().map(C::getX).collect(Collectors.toList()),
    x1
);

has the drawback that it creates an entire new list and is presumably O(n).


Is there a way to search in the stream directly, without collecting it? Or perhaps some other way to search the original list without having to construct a dummy item?


In the absence of a better approach, I would do this:

public class MappedView<T,E> extends AbstractList<E> {

    public static <T,E> MappedView<T,E> of(List<T> backingList, Function<T,E> f){
        return new MappedView<>(backingList, f);
    }

    private final List<T> backingList;
    private final Function<T,E> f;

    private MappedView(List<T> backingList, Function<T,E> f){
        this.f = f;
        this.backingList = backingList;
    }

    @Override
    public E get(int i) {
        return f.apply(backingList.get(i));
    }

    @Override
    public int size() {
        return backingList.size();
    }    
}

and then

Collections.binarySearch(
    MappedView.of(cs, c -> c.getX()),
    x1
);
like image 272
Museful Avatar asked Oct 12 '15 17:10

Museful


People also ask

Can binary search find multiple values?

As Tim Roberts said that the BinarySearch is used to search the entire sorted List<T> for an element using the default comparer and returns the zero-based index of the element. So you can't get multiple values.

Can we access middle element directly in binary search?

Binary Search Algorithm assumes that one has direct access to middle element in the list. This means that the list must be stored in some typeof linear array. I read this and also recognised that in Python you can have access to the middle element at all times.

Can you binary search a list?

Binary search is an efficient algorithm for finding an item from a sorted list of items. It works by repeatedly dividing in half the portion of the list that could contain the item, until you've narrowed down the possible locations to just one.

How do you find the number of iterations in a binary search?

Mathematically Maximum iteration possible (Assuming case of only integer type) is = ceil( log2 ( initial_r - initial_l ) ) base of log is 2 because every time we are diving our range in half by taking a mid and switching to one of the half.


2 Answers

Since you are in control of the Comparator implementation, you can implement a symmetric comparison function that allows to compare instances of C with values of the desired property, i.e. int values (wrapped as an Integer) in case of your x property. It helps to know the factory methods comparing… in the Comparator interface which save you from repeating the code for both sides of the comparison:

int index = Collections.binarySearch(cs, x1,
    Comparator.comparingInt(o -> o instanceof C? ((C)o).getX(): (Integer)o));

That way you can search for the int value x1 without wrapping it in a C instance.

like image 172
Holger Avatar answered Sep 19 '22 06:09

Holger


Another approach might be this:

private static class C {
    private final int x;
    // lots more fields be here

    private C() {
    }

    public C(int x, /* lots more arguments here */) {
        this.x = x;
        // lots more fields initialized here
    }

    public int getX(){
        return x;
    }

    public static C viewForX(int x) {
        C viewInstance = new C();
        viewInstance.x = x;
        return viewInstance;
    }
}

Collections.binarySearch(cs, 
    C.viewForX(x1), 
    (a,b) -> Integer.compare(a.getX(),b.getX()));

Or, if you don't mind a dependency, you can do this with Guava:

List<Integer> xs = Lists.transform(cs, C::getX());
Collections.binarySearch(xs, x1);
like image 35
sh0rug0ru Avatar answered Sep 21 '22 06:09

sh0rug0ru