查表法(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