3.1.3 用例举例
在学习它的实现之前我们还是应该先看看如何使用它。相应的我们这里考察两个用例:一个用来跟踪算法在小规模输入下的行为测试用例和一个来寻找更高效的实现的性能测试用例。
3.1.3.1 行为测试用例
为了在小规模的的输入下跟踪算法的行为,我们用一下测试用例测试我们对符号表的所有实现。这段代码会从标准输入接受多个字符串,构造一张符号表来将i 和第i 个字符串相关联,然后打印符号表。我们假设所有的字符串都只有一个字母。一般我们会使用”S E A R C H E X A M P L E”。按照我们的约定,用例会将键S和0,键R和3关联起来,等等。但E的值是12(而非1或者6),A的值为8(而非2),因为我们的关联性数组以为着每个键的顺序是不确定的(这和具体实现有关);对于有序符号表,用例应该将键按顺序打印出来。这是一种引索用例,,它是我们将在后面讨论的一种重要的符号表应用的一个特殊情况。
测试用例的实现代码如下所示。测试用例的键、值及输出如下图所示。
3.1.3.2 性能测试用例
FrequencyCounter用例会从标准输入中得到的一列字符串并记录每个(长度至少达到指定的数值)字符串的出现次数,然后遍历所有键并找出出现频率最高的键。这是一种字典。这个用例回答了一个简单的问题:哪个(不小于指定长度的)单词在一段文字中出现的频率最高?在本章中,我们会用这个用例以及三段文字来进行行能呢个测试:狄更斯的《双城记》中的第五行(tinyTale.txt),《双城记》全书(tale.txt),以及一个知名的叫做Leipzig Corpora Collection 的数据库(Leipzig1M.txt)。
1 public class FrequencyCounter { 2 3 /** 4 * Reads in a command-line integer and sequence of words from 5 * standard input and prints out a word (whose length exceeds 6 * the threshold) that occurs most frequently to standard output. 7 * It also prints out the number of words whose length exceeds 8 * the threshold and the number of distinct such words. 9 */ 10 public static void main(String[] args) { 11 int distinct = 0, words = 0; 12 int minlen = Integer.parseInt(args[0]); 13 ST<String, Integer> st = new ST<String, Integer>(); 14 15 // compute frequency counts 16 while (!StdIn.isEmpty()) { 17 String key = StdIn.readString(); 18 if (key.length() < minlen) continue; 19 words++; 20 if (st.contains(key)) { 21 st.put(key, st.get(key) + 1); 22 } 23 else { 24 st.put(key, 1); 25 distinct++; 26 } 27 } 28 29 // find a key with the highest frequency count 30 String max = ""; 31 st.put(max, 0); 32 for (String word : st.keys()) { 33 if (st.get(word) > st.get(max)) 34 max = word; 35 } 36 37 StdOut.println(max + " " + st.get(max)); 38 StdOut.println("distinct = " + distinct); 39 StdOut.println("words = " + words); 40 } 41 }
以上代码为符号表的用例,该用例统计了标准输入中各个单词的出现频率,然后将频率最高的单词打印出来。命令行参数指定了表中的键的最短长度。
研究符号表处理大型文本的性能要考虑来两个方面的因素:首先,每个单吃都会被作为键进行搜索,因此处理性能和输入文本的单词总量必然有关;其次,输入的每个电磁都会被存入符号表(输入中不重复单词的总数也就是所有键都被插入以后符号表的大小),因此输入流中的不同的单词的总数也是相关的。我们需要这两个量来估计FrequencyCounter的运行时间。我们会在学习了一些算法之后再回头说明一些细节,但你应该对类似这样的符号表应用的需求有一个大致的印象。
很多例子都提出了一个简单的问题:我们的实现能够在一张用多次get()和put()方法构造出的巨型符号表中进行大量的get()操作吗?如果我们的查找操作不多,那么任意实现都能够满足需要。但没有一个高效的符号表作为基础是无法使用FrequencyCounter这样的程序来处理大型问题的。FrequencyCounter是一种极为常见的应用的代表,它的这些特性也是许多其他符号表应用的共性:
混合使用查找和插入的操作
大量的不同键
查找操作比插入操作多得多
虽然不可预测,但查找和插入操作的使用模式并非随机
我们的目标就是实现一种符号表来满足这些能够解决典型的实际问题的用例需要。