本次实验需要我们首先自己构建一套ADT,完成一个图的ADT的构造,并且用自己构造的这个图去实现诗意漫步任务。前半部分自己完成这套ADT的任务问题不太大,按部就班就好。在泛型参数这一块我感觉比较陌生,需要记录一下。
- Problem 3: Implement generic Graph<L>
- Make the implementations generic
这一块任务是将两个实例类中的所有String类的参数替换为泛型的参数,包括声明、函数参数、返回值和Rep。
首先要了解什么是泛型。
泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
这里以一个接口方法展示如何定义一个泛型方法:
在返回类型Graph<L>前出现了一个<L>,这个<L>表示的是声明此方法持有一个类型L,也可以理解为声明此方法为泛型方法。
Graph<L>指明了该方法的返回类型,指明泛型L的具体类型。定义泛型方法时,必须在返回值前边加一个,来声明这是一个泛型方法,持有一个泛型T,然后才可以用泛型T作为方法的返回值。
类似的,在接口的实现方法中也是要声明出来的:
接下来简单谈谈如何实现诗意漫步任务。
Implement GraphPoet
Rep:private final Graph<String> graph = Graph.empty();
方法 | 描述 |
Public GraphPoet(File corpus) | 该方法需要我们从一个文件里读入单词并且构造词料库,用一个BufferReader类对象读入文件,一行一行地读入到一个字符串line中,并用字符串中的空格作为分隔存入一个字符串List容器words。此时我们便得到了所有的单词。利用for循环,每次利用相邻两点在图中构造一条有向边。 |
Private void checkRep() | 检查graph不能为空 |
Public String poem(String input) | 如果input为空,直接返回一个空字符串。否则,把输入的字符串以空格分割成单词存入List类型的words中,然后遍历words,判断第i个单词和第i+1个单词是否有桥接词的存在,即以第i个单词为起点,在创建好的graph中找到它的所有终点即为所有可能成为桥接词的点,再分别以所有的终点为起点找它们的终点,如果某个点的终点是我们的第i+1个单词,那么它就是我们找到的桥接词,再比较所有找到的桥接词的路径长度:第i个单词到桥接词的权重+桥接词到第i+1个单词的权重,取这个值最大时所经过的桥接词。然后把找到的桥接词添加到待输出的字符串中,如果没有找到桥接词,则不需要这一步。最后把得到的字符串返回。 |
public String toString() | @Override 重写toString方法 |
来看看第一个方法的一种实现:
首先用List类对象words来存放所有的单词,然后进入for循环,检查每两个单词之间的桥接词。
source是当前循环的单词,target为下一个单词,首先将这两个单词转换为小写(注意因为图中存放的单词都要是小写的),检查map中是否有这样的一个key:source+"to"+target,这样的key就是为了将这两个单词联系起来。如果没有就将这个key添加进去,并且value设置为1。否则,将该key当前的value值加1。此时我们就得到了这两个点之间的边的权重。利用set方法将该边设置权重。
再来看看poem方法实现吧:
这个方法要求我们利用现有的词库(也就是我们刚刚创建的图),将输入的字符串构造为一首诗。
首先还是将输入的字符串的单词都分隔开,存到List类对象words中。用StringBuilder类对象来保存要输出的poem,可变类方便我们改变内容。于是我们又用相似的办法,利用for循环。具体如何操作在上面表格已经写的比较清楚,不再赘述。