《代码大全》之表驱动法

表驱动法是一种编程模式——从表里查找信息而不是使用逻辑语句(if 和 case)。事实上,凡是能通过逻辑语句来选择的事务,都可以通过查表来选择。对简单的情况而言,使用逻辑语句更为容易和直白。但随着逻辑链的越来越复杂,查表法也就愈发显得更具吸引力。

表驱动法使用总则

例子一:
使用复杂的逻辑对字符分类

if((('a' <= inputChar) && (inputChar <= 'z')) || 
(('A' <= inputChar) && (inputChar <= 'Z‘))){
    charType = CharacterType.Letter;
}else if ((inputChar == ' ') || (inputChar == ',') || (inputChar == '.') || (inputChar == '!') || (inputChar == '(') || (inputChar == ')') || (inputChar == ':') || (inputChar == ';') || (inputChar == '?') || (inputChar == '-')) {
    charType = CharacterType.Punctuation;
} else if (('0' <= inputChar) && (inputChar <= '9')){
    charType = CharacterType.Digit;
}

使用一个查询表,就可以把每个字符的类型保存在一个用字符编码访问的数组里,那么上述代码可以替换为:

charType = charTypeTable[ inputChar ];

使用表驱动法,必须要解决两个问题。

1,怎样从表中查询条目。 比如你希望把数据按月进行分类,那么创建一个月份表是非常直截了当的,你可以用一个下标从 1 到 12 的数组实现它。而另一些数据可能很难直接用于查表。

2,你应该在表里存些什么。有
时候,表查询出来的结果是数据,有时候,表查询出来的结果是动作,或者有时候,查出来的是子程序的引用。无论何种情况,表都会变得更为复杂。

直接访问表

之所以称为“直接访问”的,是因为你无须绕很多复杂的圈子就能够在表里面找到你想要的信息。

例子二:一个月中的天数

if (month == 1) {
    days = 31;
} else if ( month == 2) {
    days = 28;
}
//以下省略。。知道这么回事就行了吧。。

现在,你无需再写那条长的 if 语句,只需要:

days = daysPerMonth( month-1 );

例子三:保险费率
假设你在写一个计算医疗保险费率的程序,这些费率是随着年龄、性别、婚姻状况以及吸烟与否的不同情况而变化的。

//一堆 if else
//代码略

一种显而易见的改进就是把在每一年龄的费率存在一个独立数组里。更好的做法是把这些费率存入所有因素索引的数组里,而不仅仅是按年龄索引。

当数据无法提供直接的键值的时候,你可以:

1,复制信息从而能直接使用键值。比如,18岁以下的年龄的费率都是一样的,你可以为0到17岁都复制一份这样的费率,然后直接用年龄作为键值。这样做的缺点在于,复制生成的冗余信息会浪费空间,并且表中存在错误的可能性也增加了。

2,转换键值以使其能够直接使用。比如,想要筛选出0到17的,还有66以上的,需要将这两部分转换成两个键值。

max( min( 66,age ), 17 );

通过以上代码,可以生成位于 17 到 66 之间的键值。又比如,在另一个程序里,age 不是以 1,而是以 5 为步数,那么就需要将所有 age 除以 5 来构造这样的键值。

3,把键值转换提取成独立的子程序。比如 Java 中的 HashMap。

索引访问表

当使用索引的时候,先用一个基本类型的数据从一张索引表中查处一个键值,然后再用这一键值查出你感兴趣的主数据。

第一项优点是,如果主查询表中每一条记录都很大,那么创建一个浪费了很多空间的索引数组所用的空间就会更小。
第二项优点是,即使你用了索引以后没有节省内存空间,操作位于索引中的记录有时也要比操作位于主表中的记录更方便廉价。

阶梯访问表

阶梯结构的基本思想是,表中的记录对于不同的数据范围有效,而不是对不同的数据点有效。

为了使用阶梯方法,你要把每一区间的上限写入一张表里,然后写一个循环,按照各区间的上限来检查分数。当分数第一次超过某个区间的上限时,你就知道相应的等级了。

double[] range = {50.0, 65.0, 75.0, 90.0, 100.0};
String[] grade = {"F", "D", "C", "B", "A"};
maxGradeLevel = grade.length - 1;

gradeLevel = 0;
studentGrade = "A";
while((studentGrade == "A") && (gradeLevel < maxGradeLevel)){
    if (studentScore < range[gradeLevel]) {
        studentGrade = grade[gradeLevel];
    }
    gradeLevel++;
}

需要注意的一些细节:
1,留心端点。
2,考虑用二分查找取代顺序查找。
3,考虑用索引访问来取代阶梯技术