简体   繁体   English

如何二进制搜索List的元素的一个字段

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

Let C be a class defined (partially) by C是(部分)定义的类

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() . 并且让cs成为实现RandomAccessList<C> ,并在C.getX()上排序。

What would be the standard approach to performing a binary search in cs for x1 on C.getX() ? 什么是标准的做法,以在执行二进制搜索csx1C.getX() (In other words, pretending that each element c is replaced by c.getX() and then we search for x1 among those integers.) (换句话说,假装每个元素c都被c.getX()替换,然后我们在这些整数中搜索x1 。)


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 ). 它的缺点是它需要构造一个新的C (可能需要大量的伪参数和关于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). 它的缺点是它创建了一个完整的新列表,大概是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
);

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, ie int values (wrapped as an Integer ) in case of your x property. 由于您可以控制Comparator实现,因此可以实现对称比较函数,该函数允许将C实例与所需属性的值进行比较,即在x属性的情况下将int值(包装为Integer )进行比较。 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: 它有助于了解comparing…的工厂方法comparing… ,这样Comparator重复比较两侧的代码:

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. 这样,您可以搜索intx1而不将其包装在C实例中。

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 : 或者,如果您不介意依赖,可以使用Guava执行此操作:

List<Integer> xs = Lists.transform(cs, C::getX());
Collections.binarySearch(xs, x1);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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