查表法(Table-Driven-Method)主要是藉由表格的維度和對應的數值來替代冗長的判斷式(if-else or switch)
查表法根據不同的查表方式,可分為三種:
直接存取(direct access):可從表格中直接讀取數值。
索引存取(indexed access):先透過索引取出key值,再從key值取出數值。
階梯存取(stair-step access):對於不同的數值範圍有效。
直接存取:
如果要查詢月份對應的天數,最直接的寫法 e.g.,
private int getDaysOfMonth(int month)
{
if (month == 1) {
return 31;
} else if (month == 2) {
return 28;
} else if (month == 3) {
return 31;
} else if (month == 4) {
return 30;
} else if (month == 5) {
return 31;
} else if (month == 6) {
return 30;
} else if (month == 7) {
return 31;
} else if (month == 8) {
return 31;
} else if (month == 9) {
return 30;
} else if (month == 10) {
return 31;
} else if (month == 11) {
return 30;
} else if (month == 12) {
return 31;
} else {
throw new IllegalArgumentException();
}
}
可以藉由以下表格來取代
private static final int[] DAYS_OF_MONTH = {
31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};
取值如下,第7行為使用查表法的動作。
@Test
public void testGetDayOfMonth()
{
int[] months = {1,2,3,4,5,6,7,8,9,10,11,12};
for(int month : months){
int resultBySwitch = getDaysOfMonth(month);
int resultByTable = DAYS_OF_MONTH[month - 1];
assertEquals(resultBySwitch, resultByTable);
}
}
索引存取:
如果條件判斷式的變數型態不是int , 就無法使用陣列來建表,必須改用Map,而 Map 的 key 值即是索引值。e.g.
private int getDayOfMonthByName(String nameOfMonth)
{
if (nameOfMonth.equals("Jan")) {
return 31;
} else if (nameOfMonth.equals("Feb")) {
return 28;
} else if (nameOfMonth.equals("Mar")) {
return 31;
} else if (nameOfMonth.equals("Apr")) {
return 30;
} else if (nameOfMonth.equals("May")) {
return 31;
} else if (nameOfMonth.equals("Jun")) {
return 30;
} else if (nameOfMonth.equals("Jul")) {
return 31;
} else if (nameOfMonth.equals("Aug")) {
return 31;
} else if (nameOfMonth.equals("Sep")) {
return 30;
} else if (nameOfMonth.equals("Oct")) {
return 31;
} else if (nameOfMonth.equals("Nov")) {
return 30;
} else if (nameOfMonth.equals("Dec")) {
return 31;
} else {
throw new IllegalArgumentException();
}
}
使用Map取值,第1到10行為初始化Map , 第14行為使用Map取值。
Map<String, Integer> DAY_OF_MONTH = new HashMap<String,Integer>();
String[] nameOfMonth = new String[] {
"Jan", "Feb", "Mar", "Apr", "May","Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};
Integer[] numberDayOfMonth = {
31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};
for (int i = 0; i < nameOfMonth.length; ++i) {
DAY_OF_MONTH.put(nameOfMonth[i], numberDayOfMonth[i]);
}
for(String name : nameOfMonth){
int resultByMethod = getDayOfMonthByName(name);
int resultByMap = DAY_OF_MONTH.get(name);
assertEquals(resultByMethod, resultByMap);
}
階梯存取:
假設現在要對不同分數給予等級的判斷, e.g.
private String getNameOfGradeByMethod(int score)
{
if (score >= 90) {
return "A";
} else if (score >= 80 && score < 90) {
return "B";
} else if (score >= 70 && score < 80) {
return "C";
} else if (score >= 60 && score < 70) {
return "D";
} else if (score < 60) {
return "F";
} else {
throw new IllegalArgumentException();
}
}
需要建立2個對應的table , 以及求值的函式(getNameOfGradeByTable)。 e.g.
private static final String NAME_OF_GRADE_LEVEL[] = {
"A", "B", "C", "D", "F"
};
private static final int NUMBER_OF_GRADE_LEVEL[] = {
90, 80, 70, 60
};
private String getNameOfGradeByTable(int score)
{
int gradeLevel = 0;
while (NAME_OF_GRADE_LEVEL[gradeLevel] != NAME_OF_GRADE_LEVEL[NAME_OF_GRADE_LEVEL.length - 1]) {
if (score < NUMBER_OF_GRADE_LEVEL[gradeLevel]) {
++gradeLevel;
} else {
break;
}
}
return NAME_OF_GRADE_LEVEL[gradeLevel];
}
雖然整體看起來比判斷式來的長,但若是接下來需要增加更多的判斷如 60, 50, 40, 30, 20, 10。
對於查表法只要在 table 加入相對應數值即可。
也適合用於沒有規則變化的求值。
事實上查表法無法去除掉原判斷式的邏輯,它只是把邏輯搬移到表中。表格的複雜度會跟著原判斷式的邏輯成正比。
另外也需要提供空間儲存表格。
查表法可以減少程式碼的長度,但無法簡化程式碼。
對可讀性的幫助並不大,反而需要完全理解表格才能修改原功能或是增加新功能。
因此最適合的情況為冗長但判斷邏輯簡單的條件式。
更複雜的查表法範例 ref