天天看點

一文道盡“表驅動法”

一文道盡“表驅動法”

什麼是表驅動法?

是一種程式設計模式,從表裡查找資訊而不使用邏輯語句(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)如何從表中查資料?

  • 直接通路
  • 索引通路
  • 階梯通路

2)在表裡存些什麼?

  • 資料
  • 動作(action)-描述該動作的代碼/該動作的子程式的引用。

1、直接通路

和所有直接查詢表一樣,直接通路代替了更為複雜的邏輯控制結構。之是以說他們是“直接通路”的,是因為你無須繞很多複雜的圈子就能夠在表裡面找到想要的資訊。

例子:

假設你需要計算每個月中的天數。

正常笨方法如下:

if(month == 1) {
    days = 31;
} else if (month = 2){
    days = 28;
} else if (month = 3){
    days = 31;
} else if (month = 4){
    days = 30;
} else if (month = 5){
    days = 31;
} else if (month = 6){
    days = 30;
} else if (month = 7){ 
    days = 31;
} else if (month = 8){
    days = 31;
} else if (month = 9){  
    days = 30;
} else if (month = 10){
    days = 31;
} else if (month = 11){
    days = 30;
} else if (month = 12){ 
    days = 31;
}      

把這些資料存到一張表裡,建立這張表:

[31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]      

這樣就隻需一條簡單的數組通路語句就可以了:

charType = charTypeTable[inputChar];      

當然,我們希望能夠将資料作為鍵值直接通路表,這樣既簡單又快速,但是問題或者資料通常并不是這樣友好。引出了構造查詢鍵值的方法:

1)複制資訊進而能夠直接使用鍵值

      使 age 能像鍵值一樣用于費率表的種查詢方法是将 1-17 歲之間的年齡都複制一份 18 歲以下的費率,然後直接用該 age 鍵值來通路表。同樣也可以用同樣的方法來處理 66 歲以上的情況。

這樣優點在于表自身結構非常簡單那,通路表的邏輯也很簡單;

缺點在于複制生成的備援資訊會浪費空間,也即是利用空間換效率。

2)轉換鍵值以使其能夠直接使用

      使 age 能像鍵值一樣用于費率表查詢的第二個方法是用一個函數将 age 轉換為另一個數值。在此例子中,該函數必須把所有介于 1-17 直接的年齡轉換成一個鍵值,例如 17,同時把所有超過 66 的年齡都轉換成另一個鍵值,例如 66。

這樣在檢索前可以用 min()和 max()函數來做這一轉換。

例如,你可以用下述表達式:max(min(66, age), 17) 來生成一個介于 17-66 之間的表鍵值。

3)把鍵值轉換提取城獨立子程式

      如果你必須要構造一些資料來讓它們像表鍵值一樣使用,那就把資料到鍵值的轉換操作提取成獨立的子程式。這樣可避免在不同位置執行了不同的轉換,也使得轉換操作修改起來更加容易。

如在程式語言中編寫邏輯函數 KeyFromAge(),甚至使用 HashMap 來定義好邏輯上的鍵值映射關系也是 OK 的。

2、索引通路

有時隻用一個簡單的數學運算還無法把 age 這樣的資料轉換成為表鍵值,這種情況可以通過索引通路的方法加以解決。

索引應用:先用一個基本類型的資料從一張索引表中查出一個鍵值,然後在用這一鍵值查出需要的主資料。

舉例:

有百餘件商品,商店物品編号(範圍 0000-9999)

建立兩張表:索引表(0-9999),物品(查詢)表(0-100)

一文道盡“表驅動法”

索引表與查詢

索引通路有兩個優點:

  • 如果主查詢表的每條記錄都很大,那建立一個浪費了很多空間的數組所用的空間,要比建立主查詢表所用的空間小得多。
  • 操作索引中的記錄比操作主查詢表的的記錄更友善,編寫到表裡面的資料比嵌入代碼的資料更容易維護。
例如:有一張員工姓名、雇傭時間和薪水的表,你可以生成一個索引按照員工姓名通路該表;生成另一個索引表按照雇傭時間來通路該表;以及生成第三個索引按照薪水來通路該表。

3、階梯通路

這種通路方法不像索引結構那樣直接,但是它要比索引通路方法節省空間。

階梯結構的基本思想:表中的記錄對于不同資料範圍有效,而不是對不同的資料點有效。

一文道盡“表驅動法”

通過命中的階梯層次确定其歸類

一個等級評定的應用程式,其中“B”記錄所對應的範圍是 75.0%-90.0%

>= 90.0%      A

<90.0%         B

<75.0%         C

<65.0%         D

<50.0%         F

這種劃分範圍用在查詢表中是不合适的,因為你不能用簡單的資料轉換函數來把表鍵值轉換成 A-F 字母所代表的等級。用索引也不合适,因為這裡用的是浮點數。

在應用階梯方法的時候,必須謹慎的處理範圍的端點。

Dim rangeLimit() As Double = {50.0, 65.0, 75.0, 90.0, 100.0}
Dim grade() As String={"F", "D", "C", "B", "A"}
maxGradeLevel = grade.Length - 1

//assign a grad to a student based on the student's score
GradeLevel= 1 
StudentGrade = ”A" 
while( StudentGrade = "A" and GradeLevel < MaxGradeLevel )
        if( StudentScore < RangeLimit( GradeLevel ) ) then
            StudentGrade = Grade ( GradeLevel)   
    end if    
            GradeLevel = GradeLevel + 1 
 Wend      

與其他表驅動法相比,這種方法的優點在于它很适合處理那些無規則的資料。

在使用階梯通路時需要注意的一些細節:

1)留心邊界端點

注意邊界:< 與 <=,确認循環能夠在找出最高一級區間後恰當地終止。

2)考慮用二分查找取代順序查找

如果清單很大,可以把它替換成一個準二分查找法,從頭查找是很耗費性能的

3)考慮用索引通路來取代階梯通路

階梯通路中的查找操作可能會比較耗時,如果執行速度很重要,那可以考慮用索引通路來取代階梯查找,即以犧牲存儲空間來換取速度。

總結

  1. 表驅動法提供了一種複雜的邏輯和繼承結構的替換方案。如果你發現自己對某個應用程式的邏輯或者繼承關系感到困惑,那是否可以通過一個查詢表來加以簡化。
  2. 使用表的關鍵決策是決定如何去通路表,可以采取直接通路、索引通路或階梯通路
  3. 使用表的另一項關鍵決策是決定如何去把什麼内容放入表中
  4. 需要儲存浮點數和範圍數時,使用階梯通路的形式。

- END -

作者:架構精進之路,十年研發風雨路,大廠架構師,CSDN 部落格專家,專注架構技術沉澱學習及分享,職業與認知更新,堅持分享接地氣兒的幹貨文章,期待與你一起成長。

關注并私信我回複“01”,送你一份程式員成長進階大禮包,歡迎勾搭。

Thanks for reading!

繼續閱讀