Java 比较器简记(1)

上午一个朋友在群里推荐每日打卡,题目《找到K个最接近的元素》,这道题推荐使用二分查找来做。这里写这边文章的主要目的是为了记录Java的比较器是怎样进行比较的。因为官方推荐了一种做法,使用Java8的新特性—Lambda表达式来做,觉得很有意思,就花了些时间琢磨了一下,并记录下来。

通常情况下,若要对数组元素进行排序,Java中的规定是元素对应的类要么实现Comparable< T >接口,即实现该接口中的唯一一个方法—public int compareTo(T o),要不就要实现Comparator< T >接口,并至少需要实现该接口中的int compare(T o1, T o2)实例方法。这里推荐一篇文章,这篇文章的作者通过举例说明了针对自定义数据类型,采用两种策略说明了如何进行比较的过程。这里不再赘述。

在这里,就单独谈谈在Java8中的函数式表达式中中如何使用比较器的。首先,自定义数据类型如下:

/**
 * Class {@code Person} is a class to validate the {@link java.util.Comparator}.
 *
 * @author Vimist
 */
class Person {

    private String name;

    private int age;

    private double grade;

    public Person(String name, int age, double grade) {
        this.name = name;
        this.age = age;
        this.grade = grade;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public double getGrade() {
        return grade;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", grade=" + grade +
                '}';
    }
}

这这个类中,并没有实现Comparable< T >或者Comparator< T >接口。接下来,在主函数中,先初始化一些对象,然后将这个对象排序输出。排序原则:首先根据成绩(升序或者降序)排序;若成绩相同,则根据姓名;若姓名相同(实际情况当然是不可能的啦,这里主要是为了做演示),则根据年龄(升序或者降序)来排序。初始化对象如下:

        Person person1 = new Person("zzz", 20, 30);
        Person person2 = new Person("bbb", 11, 20);
        Person person3 = new Person("bba", 10, 20);
        Person person4 = new Person("bba", 11, 20);
        Person[] people = {person1, person2, person3, person4};

首先,仅仅输入按照成绩升序排序的结果:

System.out.println(Arrays.stream(people).sorted(Comparator.comparing(Person::getGrade)).collect(Collectors.toList()));

输出结果如下:

[Person{name='bbb', age=11, grade=20.0}, Person{name='bba', age=10, grade=20.0}, Person{name='bba', age=11, grade=20.0}, Person{name='zzz', age=20, grade=30.0}]

通常情况下,成绩都是按照降序排序,因此

        System.out.println(Arrays.stream(people)
                .sorted((p1, p2) -> p2.getGrade() > p1.getGrade() ? 1 : p2.getGrade() == p1.getGrade() ? 0 : -1)
                .collect(Collectors.toList()));

这里需要注意的地方是,这里的sorted()函数的参数是一个Comparator接口,而这个接口是不能直接使用的。因此,这里将代码直接传给Comparator接口。更多细节可以参考Java的匿名类相关知识点。另外,由于是逆序,因此需要保证p2.getGrade() > p1.getGrade()才能满足逆序条件。

输出结果如下:

[Person{name='zzz', age=20, grade=30.0}, Person{name='bbb', age=11, grade=20.0}, Person{name='bba', age=10, grade=20.0}, Person{name='bba', age=11, grade=20.0}]

紧接着,需要根据姓名排序按照升序排序。如果说将collect(Collectors.toList())得到的链表再按照姓名sort一下,那个原先的排序顺序就会被打乱。因此,需要再第一步比较的基础上,修改部分代码,将按照成绩逆序排序的过程以匿名函数的形式提取出来如下:

Comparator<Person> comparatorByGrade = (o1, o2) -> o2.getGrade() > o1.getGrade() ? 1 : o2.getGrade() == o1.getGrade() ? 0 : -1;

这里的comparatorByGrade是一个Comparator类的匿名子类,其包含父类的所有静态函数。通过再该对象后使用thenComparing( Function<? super T, ? extends U> keyExtractor)即可实现再第一步按照成绩逆序排序的基础上,再按照姓名升序排序,代码如下:

        System.out.println(Arrays.stream(people)
                .sorted(comparatorByGrade.thenComparing(Person::getName))
                .collect(Collectors.toList()));

输出结果如下:

[Person{name='zzz', age=20, grade=30.0}, Person{name='bba', age=10, grade=20.0}, Person{name='bba', age=11, grade=20.0}, Person{name='bbb', age=11, grade=20.0}]

同样第三步,再之前两步的基础上,再按照年龄逆序排序,这里就直接给出代码如下:

Comparator<Person> comparatorByAge = (p1, p2) -> p2.getAge() > p1.getAge() ? 1 : p2.getAge() == p1.getAge() ? 0 : -1;
        System.out.println(Arrays.stream(people)
                .sorted(comparatorByGrade.thenComparing(Person::getName).thenComparing(comparatorByAge))
                .collect(Collectors.toList()));

输出结果如下:

[Person{name='zzz', age=20, grade=30.0}, Person{name='bba', age=11, grade=20.0}, Person{name='bba', age=10, grade=20.0}, Person{name='bbb', age=11, grade=20.0}]

结果符合基本预期。完整的主函数代码如下:

    public static void main(String[] args) {
        Person person1 = new Person("zzz", 20, 30);
        Person person2 = new Person("bbb", 11, 20);
        Person person3 = new Person("bba", 10, 20);
        Person person4 = new Person("bba", 11, 20);
        Person[] people = {person1, person2, person3, person4};

        Comparator<Person> comparatorByGrade = (o1, o2) -> o2.getGrade() > o1.getGrade() ? 1 : o2.getGrade() == o1.getGrade() ? 0 : -1;
        Comparator<Person> comparatorByAge = (p1, p2) -> p2.getAge() > p1.getAge() ? 1 : p2.getAge() == p1.getAge() ? 0 : -1;
        
        System.out.println(Arrays.stream(people)
                .sorted(comparatorByGrade.thenComparing(Person::getName).thenComparing(comparatorByAge))
                .collect(Collectors.toList()));
    }

最后,回到题目《找到K个最接近的元素》。根据题目的要求,需要从一个数组中找到最接近x的k个元素。这里先给出代码如下:

        return Arrays.stream(arr).boxed()
                .sorted((n1, n2) -> n1 == n2 ? n1 - n2 : Math.abs(n1 - x) - Math.abs(n2 - x))
                .limit(k).sorted().collect(Collectors.toList());

首先,通过Arrays.stream()将数组转换为一个数据流(IntStream)。由于数组的元素是基本类型,因此,通过boxed()将流中的每个元素装箱,封装成对象形式,然后将流中的数据进行排序。sorted默认按照升序排序,而排序条件是按照元素距离给定值x的距离的升序排序。可以将排序后的结果收集起来输出如下:

int[] arr = {0, 0, 1, 2, 3, 3, 4, 7, 7, 8};
int k = 6, x = 5;

[4, 3, 3, 7, 7, 2, 8, 1, 0, 0]

可以发现,sorted结果符合预期,排在最前面的值距离给定值5最接近。然后通过limit(long maxSize)取出前k个元素。但是这些元素是乱序的,因此,再次调用sorted(),使用默认排序规则升序排序,最后收集成一个链表返回。这里给出最后运行结果:

[2, 3, 3, 4, 7, 7]

上述的Lamda表达式代码在LeetCode上是可以直接运行的,虽然不是最快的(大概100ms左右,而使用二分查找法大概在4ms左右),但是通过Lamda表达式去解决问题可以看作是对Java的一种探索吧。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值