为二进制搜索编写复合对象的比较器

时间:2009-07-15 21:58:00

标签: java binary-search comparator

我有一个类和实例列表,看起来像这样(字段名称已更改以保护无辜/专有):

public class Bloat
{
    public long timeInMilliseconds;
    public long spaceInBytes;
    public long costInPennies;
}

public class BloatProducer
{
    final private List<Bloat> bloatList = new ArrayList<Bloat>();
    final private Random random = new Random();
    public void produceMoreBloat()
    {
       int n = bloatList.size();
       Bloat previousBloat = (n == 0) ? new Bloat() : bloatList.get(n-1);
       Bloat newBloat = new Bloat();
       newBloat.timeInMilliseconds = 
          previousBloat.timeInMilliseconds + random.nextInt(10) + 1;
       newBloat.spaceInBytes = 
          previousBloat.spaceInBytes + random.nextInt(10) + 1;
       newBloat.costInPennies = 
          previousBloat.costInPennies + random.nextInt(10) + 1;
       bloatList.add(newBloat);
    }
    /* other fields/methods */

    public boolean testMonotonicity()
    {
    Bloat previousBloat = null;
    for (Bloat thisBloat : bloatList)
            {
               if (previousBloat != null)
               {
                  if ((previousBloat.timeInMilliseconds 
                     >= thisBloat.timeInMilliseconds)
                   || (previousBloat.spaceInBytes 
                     >= thisBloat.spaceInBytes)
                   || (previousBloat.costInPennies
                     >= thisBloat.costInPennies))
                       return false;
               }
               previousBloat = thisBloat;
           }
           return true;
    }

BloatProducer bloatProducer;

列表bloatListBloatProducer内部保留,并且以这样的方式维护:它只会附加新的Bloat条记录,不会修改任何旧记录,并且每条记录都会这些领域是单调增加的,例如bloatProducer.testMonotonicity()将始终返回true

我想使用Collections.binarySearch(list,key,comparator)通过timeInMilliseconds,spaceInBytes或costInPennies字段搜索Bloat记录。 (如果数字在两个记录之间,我想找到之前的记录)

编写一系列3个Comparator类以使其工作的最简单方法是什么?我是否必须使用一个具有虚拟字段的Bloat对象的密钥用于我不搜索的密钥?

5 个答案:

答案 0 :(得分:5)

您需要为要比较的每个字段编写单独的比较器:

public class BloatTimeComparator implements Comparator<Bloat> {
    public int compare(Bloat bloat1, Bloat bloat2) {
        if (bloat1.timeInMilliseconds > bloat2.timeInMilliseconds) {
            return 1;
        } else if (bloat1.timeInMilliseconds < bloat2.timeInMilliseconds) {
            return -1;
        } else {
            return 0;
        }
    }
}

对于要比较的Bloat中的每个属性等等(您需要为每个属性创建一个比较器类)。然后使用Collections帮助器方法:

Collections.binarySearch(bloatList,  bloatObjectToFind, 
    new BloatTimeComparator());

对于binarySearch方法的Java documentation,返回值为:

  

搜索键的索引(如果它包含在列表中);否则,( - (插入点) - 1)。插入点定义为键将插入列表的点:第一个元素的索引大于键,或者list.size(),如果列表中的所有元素都小于指定的键。请注意,当且仅当找到密钥时,这才能保证返回值>> 0。

您指定的索引是哪个。

答案 1 :(得分:2)

如果您想按3个属性中的每个属性进行搜索,则需要有3个单独的Comparator

更清洁的选择是使用通用Comparator接收一个参数,告诉它要比较哪个字段。

基本的通用比较器应如下所示:

public class BloatComparator implements Comparator<Bloat>
{
    CompareByEnum field;

    public BloatComparator(CompareByEnum field) {
        this.field = field;
    }

    @Override
    public int compare(Bloat arg0, Bloat arg1) {
        if (this.field == CompareByEnum.TIME){
            // compare by field time
        }
        else if (this.field == CompareByEnum.SPACE) {
            // compare by field space
        }
        else {
            // compare by field cost
        }
    }
}

答案 2 :(得分:1)

这是编写第一个比较器的测试驱动方法:

public class BloatTest extends TestCase{
    public class Bloat {
        public long timeInMilliseconds;
        public long spaceInBytes;
        public long costInPennies;

        public Bloat(long timeInMilliseconds, long spaceInBytes, long costInPennies) {
            this.timeInMilliseconds = timeInMilliseconds;
            this.spaceInBytes = spaceInBytes;
            this.costInPennies = costInPennies;
        }
    }

    public void testMillisecondComparator() throws Exception {
        Bloat a = new Bloat(5, 10, 10);
        Bloat b = new Bloat(3, 12, 12);
        Bloat c = new Bloat(5, 12, 12);

        Comparator<Bloat> comparator = new MillisecondComparator();
        assertTrue(comparator.compare(a, b) > 0);
        assertTrue(comparator.compare(b, a) < 0);
        assertEquals(0, comparator.compare(a, c));
    }

    private static class MillisecondComparator implements Comparator<Bloat> {
        public int compare(Bloat a, Bloat b) {
            Long aTime = a.timeInMilliseconds;
            return aTime.compareTo(b.timeInMilliseconds);
        }
    }


}

答案 3 :(得分:0)

如果要对所有三个属性利用二进制搜索,则必须为它们创建比较器,并具有按比较器排序的其他列表或树集。

答案 4 :(得分:0)

测试程序(MultiBinarySearch.java),看看这些想法是否正常运行(看起来如此):

package com.example.test;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Random;

class Bloat
{
    final public long timeInMilliseconds;
    final public long spaceInBytes;
    final public long costInPennies;
    static final private int N = 100; 
    public Bloat(long l1, long l2, long l3) {
        timeInMilliseconds = l1;
        spaceInBytes = l2;
        costInPennies = l3; 
    }
    public Bloat() { this(0,0,0); }
    public Bloat moreBloat(Random r)
    {
        return new Bloat(
                timeInMilliseconds + r.nextInt(N) + 1,
                spaceInBytes + r.nextInt(N) + 1,
                costInPennies + r.nextInt(N) + 1
        );
    }
    public String toString() {
        return "[bloat: time="+timeInMilliseconds
            +", space="+spaceInBytes
            +", cost="+costInPennies
            +"]";
    }

    static int compareLong(long l1, long l2)
    {
        if (l2 > l1)
            return -1;
        else if (l1 > l2)
            return 1;
        else
            return 0;
    }

    public static class TimeComparator implements Comparator<Bloat> {
        public int compare(Bloat bloat1, Bloat bloat2) {
            return compareLong(bloat1.timeInMilliseconds, bloat2.timeInMilliseconds);
        }
    }
    public static class SpaceComparator implements Comparator<Bloat> {
        public int compare(Bloat bloat1, Bloat bloat2) {
            return compareLong(bloat1.spaceInBytes, bloat2.spaceInBytes);
        }
    }
    public static class CostComparator implements Comparator<Bloat> {
        public int compare(Bloat bloat1, Bloat bloat2) {
            return compareLong(bloat1.costInPennies, bloat2.costInPennies);
        }
    }
    enum Type { 
        TIME(new TimeComparator()), 
        SPACE(new SpaceComparator()),
        COST(new CostComparator());

        public Comparator<Bloat> comparator;
        Type(Comparator<Bloat> c) { this.comparator = c; } 
    } 
}

class BloatProducer
{
    final private List<Bloat> bloatList = new ArrayList<Bloat>();
    final private Random random = new Random();
    public void produceMoreBloat()
    {
        int n = bloatList.size();
        Bloat newBloat = 
            (n == 0) ? new Bloat() : bloatList.get(n-1).moreBloat(random);
            bloatList.add(newBloat);
    }
    /* other fields/methods */

    public boolean testMonotonicity()
    {
        Bloat previousBloat = null;
        for (Bloat thisBloat : bloatList)
        {
            if (previousBloat != null)
            {
                if ((previousBloat.timeInMilliseconds 
                        >= thisBloat.timeInMilliseconds)
                    || (previousBloat.spaceInBytes 
                        >= thisBloat.spaceInBytes)
                    || (previousBloat.costInPennies
                        >= thisBloat.costInPennies))
                    return false;
            }
            previousBloat = thisBloat;
        }
        return true;
    }
    public int searchBy(Bloat.Type t, Bloat key)
    {
        return Collections.binarySearch(bloatList, key, t.comparator);
    }
    public void showSearch(Bloat.Type t, Bloat key)
    {
        System.out.println("Search by "+t+": "); 
        System.out.println(key);
        int i = searchBy(t,key);
        if (i >= 0)
        {
            System.out.println("matches");
            System.out.println(bloatList.get(i));
        }
        else
        {
            System.out.println("is between");
            i = -i-1;
            Bloat b1 = (i == 0) ? null : bloatList.get(i-1);
            System.out.println(b1);
            Bloat b2 = (i >= bloatList.size()) ? null : bloatList.get(i);
            System.out.println("and");
            System.out.println(b2);
        }
    }
}

public class MultiBinarySearch {
    private static int N = 1000;
    public static void main(String[] args)
    {
        BloatProducer bloatProducer = new BloatProducer();
        for (int i = 0; i < N; ++i)
        {
            bloatProducer.produceMoreBloat();
        }

        System.out.println("testMonotonicity() returns "+
                bloatProducer.testMonotonicity());
        Bloat key;
        key = new Bloat(10*N, 20*N, 30*N);
        bloatProducer.showSearch(Bloat.Type.COST, key);
        bloatProducer.showSearch(Bloat.Type.SPACE, key);
        bloatProducer.showSearch(Bloat.Type.TIME, key);
        key = new Bloat(-10000, 0, 1000*N);
        bloatProducer.showSearch(Bloat.Type.COST, key);
        bloatProducer.showSearch(Bloat.Type.SPACE, key);
        bloatProducer.showSearch(Bloat.Type.TIME, key);
    }
}