什麼是表驅動法
表驅動法是一種編程模式(scheme)—— 從表中查找信息而不使用邏輯語句(if-else)。
凡是通過邏輯語句來選擇的實物,都可以通過查表來選擇
對簡單的情況而言,使用邏輯語句更爲容易和直白。但是隨着邏輯鏈複雜度的不斷升高,查表法也就越來越有優勢。
在適當的環境下,採用表驅動法,所產生的代碼會比複雜的邏輯代碼更加簡單、更以維護,執行效率也更高。
簡單舉一個表驅動法比邏輯語句更加適合的使用場景:
//一個可以返回每個月中天數的函數(爲簡單起見不考慮閏年)
int iGetMonthDays(int iMonth)
{
int iDays;
if(1 == iMonth) {iDays = 31;}
else if(2 == iMonth) {iDays = 28;}
else if(3 == iMonth) {iDays = 31;}
else if(4 == iMonth) {iDays = 30;}
else if(5 == iMonth) {iDays = 31;}
else if(6 == iMonth) {iDays = 30;}
else if(7 == iMonth) {iDays = 31;}
else if(8 == iMonth) {iDays = 31;}
else if(9 == iMonth) {iDays = 30;}
else if(10 == iMonth) {iDays = 31;}
else if(11 == iMonth) {iDays = 30;}
else if(12 == iMonth) {iDays = 31;}
return iDays;
}
使用表驅動法代碼就可以改成如下:
// 先定義一個靜態數組,這個數組用來保存一年十二個月的天數
static int aiMonthDays[] = {31,28,31,30,31,30,31,31,30,31,30,31};
int iGetMonthDays(int iMonth){
return aiMonthDays[(iMonth - 1)];
}
和查表一樣,這樣做就省去了冗餘的邏輯判斷,而且我們沒有考慮是否是閏年的情況,如果將此考慮進來,if-else代碼將會更加的長,而表驅動法的代碼將仍然很簡單。
使用表驅動法的兩個問題
搞清楚了什麼是表驅動法,我們就需要考慮如下兩個問題
- 如何訪問表
- 什麼內容應該被放入表中(後續再討論)
1.如何訪問表
在上面那個場景中,查表是是一件十分容易的事,但是當你有99,999,999條記錄呢,你首先要有能存下這麼多條記錄的數據庫,其次還要有能讓人接受的查找速度才行,如果像剛纔那樣直接訪問表肯定是不行的。
查表的方法,書中將它們分爲三類:
-
直接訪問(Direct access)
直接訪問上面已經舉出例子,就是直接查表然後查出所需內容。 -
索引訪問(Indexed access)
索引訪問就是建立一個索引,學過數據結構都知道,這種訪問方式更適合大量數據的快速查找,通過索引表間接訪問目標。 -
階梯訪問(Stair-step access)
基本思想:表中的記錄不再專注於某一具體的數據點,而是根據不同範圍進行歸類,很適合處理無規則的數據。當然對空間的需求要小於索引訪問。
舉一個例子來理解:
假設下面是某一保險公司,某一天的理賠金額情況:
上圖不同事件的理賠金額就是雜亂而又隨機的,我們不可能對每一個具體事件產生的賠償數額都設置一個函數方法進行判斷以至於繼續接下來操作,這時候就應該使用階梯訪問模式,如下圖:
這就是上面說的表中的記錄不再專注於某一具體的數據點,而是根據不同範圍進行歸類這就是階梯訪問的粗略解釋。
在使用階梯技術時需要注意一下幾點:
- 留心端點
注意>
和>=
的區別,注意選取合適的上下限,注意處理好端點,確認循環能夠在最高一級的區間後終止 - 考慮用二分查找取代順序查找
這是顯然,當列表很大時,順序查找難免會性能不佳,此時不要忘了二分查找,原因是,大多數二分查找都是爲了要找到一個數值 - 考慮用索引訪問來取代階梯技術
如果二分查找仍然無法滿足對速度的要求,此時必須考慮一種方案來替代階梯技術,此時就應該考慮用索引訪問了,用空間來換取時間。 - 把階梯表查詢操作提取成單獨的子程序
要點 key points
- 表提供了一種複雜的邏輯和繼承結構的替換方案。並且大部分情況下都可以簡化複雜的邏輯或者繼承結構。
- 使用表的第一項關鍵決策是決定如何訪問表。
- 使用表的第二項關鍵決策是決定應該把什麼內容放入表中。