Previously on OOP:
Find the pair of Doctor and Patient. Then add Patient information to Doctor and add Doctor information to Patient. Afterwards, loadData() function will read a file line by line, so as to create objects of Person and Doctor classes. Last but not lease, in this article, requirements related to information query will be implemented.
idleDoctors()函数返回没有病人的医生,并且按照字母顺序排序。在Doctor类的attributes中,有姓氏、名字和专业是String类型的,也就是含有字母的,所以只有把这三项按照字母顺序排序是make sense的。
那么,排序的标准是谁呢?这里没有明确地写出来,所以本黄鸭姑且就认为是先按照姓氏字母排序;如果姓氏相同,再按照名字排序。
另外,不论排序的标准是三个attributes中的哪一个,本需求中的排序和按照badge ID/ SSN排序是有本质区别的。如果是按照badge ID/ SSN排序,那么只要创建一个TreeMap,把badge ID/ SSN存放在key field中就可以了。而按照姓氏、名字或者专业排序,就必须要出动Comparable, Comparator, comparing中的一个。
如果选第一个,那么要让Person implementsComparable,并且override compareTo() method:
However, the most compact option is the last one,comparing()。
本黄鸭还要再补充一点,Clinic class中只有两个数据结构,一个存放的是所有Person类的object references,另一个存放的是所有Doctor类的object references。也就是说,在本项大作业中选择谁作为Stream的Source将不再是一个profound question,各位宝宝应该可以轻松化解。比如本需求,明显是和Doctor有关,和Person无关,所以Stream Source是mapDoctors。
busyDoctors()函数返回的还是Doctor实例的collection,要求医生看的病人超过平均数。这个需求和刚才那个差不多,只要把:
替换成:
就可以了。唯一剩下需要做的,就是想方设法地求出“average”的值,本黄鸭觉得以下几种方法都是可行的:
Solution 1:averagingInt() collector
Solution 2:average() function
Solution 3:counting() collector
这种编写方式相比前面两种不是非常好,因为动用了非常复杂的flatMap()函数,把Stream变成了Stream
。
doctorsByNumPatients()函数的返回值类型是List,每个String由两个字段构成,中间用“:”分割,冒号前面是医生的病人数量,后面是对应的医生信息,有badge ID,姓氏和名字,中间用空格分割。
写出单个字符串是非常容易的,因为无论是病人的数量,还是医生的信息,全部都在单个的Doctor类的object中。既可以在Doctor中添加一个toString函数,把该类的size of Patients list,badge ID,姓氏和名字整理成一个字符串;又可以在Stream中调用map函数,把每个Stream元素中的size of Patients list,badge ID,姓氏和名字取出来,整理成一个字符串。
至于把单个字符串拼接成List,那就更容易了,只要调用toList() collector就好了。
接下来是sorted in decreasing order问题,排序的标准是size of Patients list,也不是针对primary key(即badge ID/ SSN)的排序,所以要在Comparable/ Comparator/ comparing中三选一。
最后还有一个容易被忽视的问题,那就是冒号前面字段的格式被要求是“###”,三位数。那么,其他位数的数字要怎样变成三位呢?请各位宝宝看下面这段String.format()函数的介绍:
此外,把单个String的两个字段先分别存放在Map的key field和value field中也是可以的,只不过比上面这种方法复杂很多,本黄鸭非常不推荐,各位宝宝看一看就好。
前三个需求的Stream Source都选用了mapDoctors,本需求也可以选用mapPatients:
countPatientsPerSpecialization()函数的返回值类型也是List,每个字符串由两个字段构成,中间用“:”分割。冒号前面是整个科室中病人的总数,后面是科室的名称。
这个需求和前面那个需求有明显的不同,因为现在每个字符串对的信息并不是基于单个Doctor的,而是基于一个科室中所有的doctors的。因为科室的名字在结果中具有unique性质,所以我们故技重施,先把Stream整理成为一个Map,key field是specialization,value field是整个科室中病人的总数。
接下来,把Map作为Stream打开,每个Map Entry都是Stream中的一个元素。再用map()函数和String.format()函数把Map Entries整理成一个一个的字符串。最后用toList() collector把字符串收集在List中。
总之,其他问题都不是很难,本需求最难实现的是sort,要求先按照病人的数量降序排序,再按照科室的名称升序排列。本黄鸭认为有以下三种方法能实现这个需求:
Solution 1: new class
在把specialization和科室中病人的总数分别作为key field和value field加入到Map中之后,我们把Map作为Stream打开,每个Map Entry都是Stream中的一个元素。这些步骤不变。
然后,变化来了。我们分别把key field和value field作为attributes存放在一个新的类中去,这个类的名称随便,暂且就先叫GoldenDuck类吧。
最后,GoldenDuck类implements Comparable Interface,必须要override一个叫做compareTo()的函数,在里面定义比较的方法。先看存放科室中病人总数的谁大,因为要降序排列,所以返回值加一个负号;如果病人总数一样大,那么就再比较科室名称。
本黄鸭认为这个方法虽然在执行方面复杂,不仅要创建一个新类,而且既有Stream的方法也有非Stream的方法;但是理解方面却是清晰、易懂,所以非常推荐基础扎实的宝宝们使用。
Solution 2: stable sort algorithm
把specialization和科室中病人的总数分别作为key field和value field加入到Map中,现在,我们把Map改成会按照key field排序的TreeMap,也就是说,先按照科室的名称排序。
然后,我们把Map作为Stream打开,每个Map Entry都是Stream中的一个元素。这个步骤还是不变。
接下来,调用sorted()函数,按照Map Entry中的value field重新排序。那么,一个重要的问题来了,这里是重新排序,那么刚才TreeMap中的顺序就被打乱了,等于还是没有按照科室的名称升序排列。这个说法对吗?答案是不对,因为sorted()函数有stable性质,按照value field重新排序不会打乱刚才TreeMap中的排序。也就是说,需求已经被实现了。
那么,神奇的“stable性质”是什么呢?所有的排序算法是好是坏可以从以下三个角度去思考:时间复杂度,空间复杂度,是否stable。所谓stable,就是:排序前后相同元素的相对位置不变。举一个例子,现在有Chicken, Duck1, Duck2, Goose四个人按照字母降序排列,如果算法是stable的,那么在排好的list中,Duck1还是在Duck2的前面。
病人总数一样的科室在TreeMap中已经按照科室的名称排好顺序了,只要sorted()的算法是stable的,那么病人总数一样的科室的在排序结果中的相对位置不会发生变化。
请各位宝宝特别关注一下filter()函数。这个函数被调用是为了避免把没有生病的医生算作为病人,因为mapPatients中不仅仅全是病人信息,还有这家医院所有的医生信息。
另外,如果按照科室的名称不是升序排列,而是降序排列,那么TreeMap要这么编写:
Solution 3: comparing, thenComparing
一看到有先后两个排序标准的需求,很多宝宝们第一时间想到的就是comparing()和thenComparing()函数,但是必须针对单个Stream的元素使用。经过各种调查,本黄鸭发现,原来Map.Entry类中有两个函数,一个是我们常用的getValue(),还有一个是getKey(),分别可以取出Map entries中的value field和key field。这样一来,“必须针对单个Stream的元素使用”的问题就迎刃而解了。
在本段代码中,阴影部分中的collectingAndThen() collector能不能替换为counting()collector呢?答案是不行,因为counting()统计的是这个科室中医生的数量,而不是这个科室中病人的数量。
以上就是第六项大作业Medical Clinic的全部代码。
欢迎使用本黄鸭编写的小程序~
微信公众号二维码:
你的每一个“好看”,都是对我的鼓励
领取专属 10元无门槛券
私享最新 技术干货