程式碼大全學習筆記之表驅動法
資料壓倒一切。如果選擇了正確的資料結構並把一切組織的井井有條,正確的演算法就不言自明。程式設計的核心是資料結構,而不是演算法。
——Rob Pike
說明
本文基於這樣的認識:資料是易變的,邏輯是穩定的。
本文例舉的程式設計實現多為程式碼片段,但不影響描述的完整性。
本文例舉的程式設計雖然基於C語言,但其程式設計思想也適用於其他語言。
此外,本文不涉及語言相關的執行效率討論。
1 概念提出
所謂表驅動法(Table-Driven Approach)簡而言之就是用查表的方法獲取資料。此處的“表”通常為陣列,但可視為資料庫的一種體現。
根據字典中的部首檢字表查詢讀音未知的漢字就是典型的表驅動法,即以每個字的字形為依據,計算出一個索引值,並對映到對應的頁數。相比一頁一頁地順序翻字典查字,部首檢字法效率極高。
具體到程式設計方面,在資料不多時可用邏輯判斷語句(if…else或switch…case)來獲取值;但隨著資料的增多,邏輯語句會越來越長,此時表驅動法的優勢就開始顯現。
例如,用36進位制(A表示10,B表示11,…)表示更大的數字,邏輯判斷語句如下:
1 if(ucNum < 10) 2 { 3 ucNumChar = ConvertToChar(ucNum); 4 } 5 else if(ucNum == 10) 6 { 7 ucNumChar = 'A'; 8 } 9 else if(ucNum == 11) 10 { 11 ucNumChar = 'B'; 12 } 13 else if(ucNum == 12) 14 { 15 ucNumChar = 'C'; 16 } 17 //... ... 18 else if(ucNum == 35) 19 { 20 ucNumChar = 'Z'; 21 }
當然也可以用switch…case結構,但實現都很冗長。而用表驅動法(將numChar存入陣列)則非常直觀和簡潔。如:
1 CHAR aNumChars[] = {'0', '1', '2', /*3~9*/'A', 'B', 'C', /*D~Y*/'Z'}; 2 CHAR ucNumChar = aNumChars[ucNum % sizeof(aNumChars)];
像這樣直接將變數當作下陣列下標來讀取數值的方法就是直接查表法。
注意,如果熟悉字串操作,則上述寫法可以更簡潔:
1 CHAR ucNumChar = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"[ucNum];
使用表驅動法時需要關注兩個問題:一是如何查表,從表中讀取正確的資料;二是表裡存放什麼,如數值或函式指標。前者參見1.1節“查表方式”內容,後者參見1.2節“實戰示例”內容。
1.1 查表方式
常用的查表方式有直接查詢、索引查詢和分段查詢等。
1.1.1 直接查詢
即直接通過陣列下標獲取到資料。如果熟悉雜湊表的話,可以很容易看出這種查表方式就是雜湊表的直接訪問法。
如獲取星期名稱,邏輯判斷語句如下:
1 if(0 == ucDay) 2 { 3 pszDayName = "Sunday"; 4 } 5 else if(1 == ucDay) 6 { 7 pszDayName = "Monday"; 8 } 9 //... ... 10 else if(6 == ucDay) 11 { 12 pszDayName = "Saturday"; 13 }
而實現同樣的功能,可將這些資料儲存到一個表裡:
1 CHAR *paNumChars[] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}; 2 CHAR *pszDayName = paNumChars[ucDay];
類似雜湊表特性,表驅動法適用於無需有序遍歷資料,且資料量大小可提前預測的情況。
對於過於複雜和龐大的判斷,可將資料存為檔案,需要時載入檔案初始化陣列,從而在不修改程式的情況下調整裡面的數值。
有時,訪問之前需要先進行一次鍵值轉換。如表驅動法表示埠忙閒時,需將槽位埠號對映為全域性編號。所生成的埠數目大小的陣列,其下標對應全域性埠編號,元素值表示相應埠的忙閒狀態。
1.1.2 索引查詢
有時通過一次鍵值轉換,依然無法把資料(如英文單詞等)轉為鍵值。此時可將轉換的對應關係寫到一個索引表裡,即索引訪問。
如現有100件商品,4位編號,範圍從0000到9999。此時只需要申請一個長度為100的陣列,且對應2位鍵值。但將4位的編號轉換為2位的鍵值,可能過於複雜或沒有規律,最合適的方法是建立一個儲存該轉換關係的索引表。採用索引訪問既節省記憶體,又方便維護。比如索引A表示通過名稱訪問,索引B表示通過編號訪問。
1.1.3 分段查詢
通過確定資料所處的範圍確定分類(下標)。有的資料可分成若干區間,即具有階梯性,如分數等級。此時可將每個區間的上限(或下限)存到一個表中,將對應的值存到另一表中,通過第一個表確定所處的區段,再由區段下標在第二個表裡讀取相應數值。注意要留意端點,可用二分法查詢,另外可考慮通過索引方法來代替。
如根據分數查績效等級:
1 #define MAX_GRADE_LEVEL (INT8U)5 2 DOUBLE aRangeLimit[MAX_GRADE_LEVEL] = {50.0, 60.0, 70.0, 80.0, 100.0}; 3 CHAR *paGrades[MAX_GRADE_LEVEL] = {"Fail", "Pass", "Credit", "Distinction", "High Distinction"}; 4 5 static CHAR* EvaluateGrade(DOUBLE dScore) 6 { 7 INT8U ucLevel = 0; 8 for(; ucLevel < MAX_GRADE_LEVEL; ucLevel++) 9 { 10 if(dScore < aRangeLimit[ucLevel]) 11 return paGrades[ucLevel]; 12 } 13 return paGrades[0]; 14 }
上述兩張表(陣列)也可合併為一張表(結構體陣列),如下所示:
1 typedef struct{ 2 DOUBLE aRangeLimit; 3 CHAR *pszGrade; 4 }T_GRADE_MAP; 5 6 T_GRADE_MAP gGradeMap[MAX_GRADE_LEVEL] = { 7 {50.0, "Fail"}, 8 {60.0, "Pass"}, 9 {70.0, "Credit"}, 10 {80.0, "Distinction"}, 11 {100.0, "High Distinction"} 12 }; 13 14 static CHAR* EvaluateGrade(DOUBLE dScore) 15 { 16 INT8U ucLevel = 0; 17 for(; ucLevel < MAX_GRADE_LEVEL; ucLevel++) 18 { 19 if(dScore < gGradeMap[ucLevel].aRangeLimit) 20 return gGradeMap[ucLevel].pszGrade; 21 } 22 return gGradeMap[0].pszGrade; 23 }
該表結構已具備的資料庫的雛形,並可擴充套件支援更為複雜的資料。其查表方式通常為索引查詢,偶爾也為分段查詢;當索引具有規律性(如連續整數)時,退化為直接查詢。
使用分段查詢法時應注意邊界,將每一分段範圍的上界值都考慮在內。找出所有不在最高一級範圍內的值,然後把剩下的值全部歸入最高一級中。有時需要人為地為最高一級範圍新增一個上界。
同時應小心不要錯誤地用“<”來代替“<=”。要保證迴圈在找出屬於最高一級範圍內的值後恰當地結束,同時也要保證恰當處理範圍邊界。
1.2 實戰示例
本節多數示例取自實際專案。表形式為一維陣列、二維陣列和結構體陣列;表內容有資料、字串和函式指標。基於表驅動的思想,表形式和表內容可衍生出豐富的組合。
1.2.1 字元統計
問題:統計使用者輸入的一串數字中每個數字出現的次數。
普通解法主體程式碼如下:
1 INT32U aDigitCharNum[10] = {0}; /* 輸入字串中各數字字元出現的次數 */ 2 INT32U dwStrLen = strlen(szDigits); 3 4 INT32U dwStrIdx = 0; 5 for(; dwStrIdx < dwStrLen; dwStrIdx++) 6 { 7 switch(szDigits[dwStrIdx]) 8 { 9 case '1': 10 aDigitCharNum[0]++; 11 break; 12 case '2': 13 aDigitCharNum[1]++; 14 break; 15 //... ... 16 case '9': 17 aDigitCharNum[8]++; 18 break; 19 } 20 }
這種解法的缺點顯而易見,既不美觀也不靈活。其問題關鍵在於未將數字字元與陣列aDigitCharNum下標直接關聯起來。
以下示出更簡潔的實現方式:
1 for(; dwStrIdx < dwStrLen; dwStrIdx++) 2 { 3 aDigitCharNum[szDigits[dwStrIdx] - '0']++; 4 }
上述實現考慮到0也為數字字元。該解法也可擴充套件至統計所有ASCII可見字元。
1.2.2 月天校驗
問題:對給定年份和月份的天數進行校驗(需區分平年和閏年)。
普通解法主體程式碼如下:
1 switch(OnuTime.Month) 2 { 3 case 1: 4 case 3: 5 case 5: 6 case 7: 7 case 8: 8 case 10: 9 case 12: 10 if(OnuTime.Day>31 || OnuTime.Day<1) 11 { 12 CtcOamLog(FUNCTION_Pon,"Don't support this Day: %d(1~31)!!!\n", OnuTime.Day); 13 retcode = S_ERROR; 14 } 15 break; 16 case 2: 17 if(((OnuTime.Year%4 == 0) && (OnuTime.Year%100 != 0)) || (OnuTime.Year%400 == 0)) 18 { 19 if(OnuTime.Day>29 || OnuTime.Day<1) 20 { 21 CtcOamLog(FUNCTION_Pon,"Don't support this Day: %d(1~29)!!!\n", OnuTime.Day); 22 retcode = S_ERROR; 23 } 24 } 25 else 26 { 27 if(OnuTime.Day>28 || OnuTime.Day<1) 28 { 29 CtcOamLog(FUNCTION_Pon,"Don't support this Day: %d(1~28)!!!\n", OnuTime.Day); 30 retcode = S_ERROR; 31 } 32 } 33 break; 34 case 4: 35 case 6: 36 case 9: 37 case 11: 38 if(OnuTime.Day>30 || OnuTime.Day<1) 39 { 40 CtcOamLog(FUNCTION_Pon,"Don't support this Day: %d(1~30)!!!\n", OnuTime.Day); 41 retcode = S_ERROR; 42 } 43 break; 44 default: 45 CtcOamLog(FUNCTION_Pon,"Don't support this Month: %d(1~12)!!!\n", OnuTime.Month); 46 retcode = S_ERROR; 47 break; 48 }
以下示出更簡潔的實現方式:
1 #define MONTH_OF_YEAR 12 /* 一年中的月份數 */ 2 3 /* 閏年:能被4整除且不能被100整除,或能被400整除 */ 4 #define IS_LEAP_YEAR(year) ((((year) % 4 == 0) && ((year) % 100 != 0)) || ((year) % 400 == 0)) 5 6 /* 平年中的各月天數,下標對應月份 */ 7 INT8U aDayOfCommonMonth[MONTH_OF_YEAR] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; 8 9 INT8U ucMaxDay = 0; 10 if((OnuTime.Month == 2) && (IS_LEAP_YEAR(OnuTime.Year))) 11 ucMaxDay = aDayOfCommonMonth[1] + 1; 12 else 13 ucMaxDay = aDayOfCommonMonth[OnuTime.Month-1]; 14 15 if((OnuTime.Day < 1) || (OnuTime.Day > ucMaxDay) 16 { 17 CtcOamLog(FUNCTION_Pon,"Month %d doesn't have this Day: %d(1~%d)!!!\n", 18 OnuTime.Month, OnuTime.Day, ucMaxDay); 19 retcode = S_ERROR; 20 }
1.2.3 名稱構造
問題:根據WAN介面承載的業務型別(Bitmap)構造業務型別名稱字串。
普通解法主體程式碼如下:
1 void Sub_SetServerType(INT8U *ServerType, INT16U wan_servertype) 2 { 3 if ((wan_servertype & 0x0001) == 0x0001) 4 { 5 strcat(ServerType, "_INTERNET"); 6 } 7 if ((wan_servertype & 0x0002) == 0x0002) 8 { 9 strcat(ServerType, "_TR069"); 10 } 11 if ((wan_servertype & 0x0004) == 0x0004) 12 { 13 strcat(ServerType, "_VOIP"); 14 } 15 if ((wan_servertype & 0x0008) == 0x0008) 16 { 17 strcat(ServerType, "_OTHER"); 18 } 19 }
以下示出C語言中更簡潔的實現方式:
1 #define GET_BIT(var, bit) (((var) >> (bit)) & 0x1) /* 獲取var變數第bit位,編號從右至左 */ 2 const CHAR* paSvrNames[] = {"_INTERNET", "_TR069", "_VOIP", "_OTHER"}; 3 const INT8U ucSvrNameNum = sizeof(paSvrNames) / sizeof(paSvrNames[0]); 4 5 VOID SetServerType(CHAR *pszSvrType, INT16U wSvrType) 6 { 7 INT8U ucIdx = 0; 8 for(; ucIdx < ucSvrNameNum; ucIdx++) 9 { 10 if(1 == GET_BIT(wSvrType, ucIdx)) 11 strcat(pszSvrType, paSvrNames[ucIdx]); 12 } 13 }
新的實現將資料和邏輯分離,維護起來非常方便。只要邏輯(規則)不變,則唯一可能的改動就是資料(paSvrNames)。
1.2.4 值名解析
問題:根據列舉變數取值輸出其對應的字串,如PORT_FE(1)輸出“Fe”。
1 //值名對映表結構體定義,用於數值解析器 2 typedef struct{ 3 INT32U dwElem; //待解析數值,通常為列舉變數 4 CHAR* pszName; //指向數值所對應解析名字串的指標 5 }T_NAME_PARSER; 6 7 /****************************************************************************** 8 * 函式名稱: NameParser 9 * 功能說明: 數值解析器,將給定數值轉換為對應的具名字串 10 * 輸入引數: VOID *pvMap :值名對映表陣列,含T_NAME_PARSER結構體型別元素 11 VOID指標允許使用者在保持成員數目和型別不變的前提下, 12 定製更有意義的結構體名和/或成員名。 13 INT32U dwEntryNum :值名對映表陣列條目數 14 INT32U dwElem :待解析數值,通常為列舉變數 15 INT8U* pszDefName :預設具名字串指標,可為空 16 * 輸出引數: NA 17 * 返回值 : INT8U *: 數值所對應的具名字串 18 當無法解析給定數值時,若pszDefName為空,則返回數值對應的16進位制格式 19 字串;否則返回pszDefName。 20 ******************************************************************************/ 21 INT8U *NameParser(VOID *pvMap, INT32U dwEntryNum, INT32U dwElem, INT8U* pszDefName) 22 { 23 CHECK_SINGLE_POINTER(pvMap, "NullPoniter"); 24 25 INT32U dwEntryIdx = 0; 26 for(dwEntryIdx = 0; dwEntryIdx < dwEntryNum; dwEntryIdx++) 27 { 28 T_NAME_PARSER *ptNameParser = (T_NAME_PARSER *)pvMap; 29 if(dwElem == ptNameParser->dwElem) 30 { 31 return ptNameParser->pszName; 32 } 33 //ANSI標準禁止對void指標進行演算法操作;GNU標準則指定void*演算法操作與char*一致。 34 //若考慮移植性,可將pvMap型別改為INT8U*,或定義INT8U*區域性變數指向pvMap。 35 pvMap += sizeof(T_NAME_PARSER); 36 } 37 38 if(NULL != pszDefName) 39 { 40 return pszDefName; 41 } 42 else 43 { 44 static INT8U szName[12] = {0}; //Max:"0xFFFFFFFF" 45 sprintf(szName, "0x%X", dwElem); 46 return szName; 47 } 48 }
以下給出NameParser的簡單應用示例:
1 //UNI埠型別值名對映表結構體定義 2 typedef struct{ 3 INT32U dwPortType; 4 INT8U* pszPortName; 5 }T_PORT_NAME; 6 //UNI埠型別解析器 7 T_PORT_NAME gUniNameMap[] = { 8 {1, "Fe"}, 9 {3, "Pots"}, 10 {99, "Vuni"} 11 }; 12 const INT32U UNI_NAM_MAP_NUM = (INT32U)(sizeof(gUniNameMap)/sizeof(T_PORT_NAME)); 13 VOID NameParserTest(VOID) 14 { 15 INT8U ucTestIndex = 1; 16 17 printf("[%s]<Test Case %u> Result: %s!\n", __FUNCTION__, ucTestIndex++, 18 strcmp("Unknown", NameParser(gUniNameMap, UNI_NAM_MAP_NUM, 0, "Unknown")) ? "ERROR" : "OK"); 19 printf("[%s]<Test Case %u> Result: %s!\n", __FUNCTION__, ucTestIndex++, 20 strcmp("DefName", NameParser(gUniNameMap, UNI_NAM_MAP_NUM, 0, "DefName")) ? "ERROR" : "OK"); 21 printf("[%s]<Test Case %u> Result: %s!\n", __FUNCTION__, ucTestIndex++, 22 strcmp("Fe", NameParser(gUniNameMap, UNI_NAM_MAP_NUM, 1, "Unknown")) ? "ERROR" : "OK"); 23 printf("[%s]<Test Case %u> Result: %s!\n", __FUNCTION__, ucTestIndex++, 24 strcmp("Pots", NameParser(gUniNameMap, UNI_NAM_MAP_NUM, 3, "Unknown")) ? "ERROR" : "OK"); 25 printf("[%s]<Test Case %u> Result: %s!\n", __FUNCTION__, ucTestIndex++, 26 strcmp("Vuni", NameParser(gUniNameMap, UNI_NAM_MAP_NUM, 99, NULL)) ? "ERROR" : "OK"); 27 printf("[%s]<Test Case %u> Result: %s!\n", __FUNCTION__, ucTestIndex++, 28 strcmp("Unknown", NameParser(gUniNameMap, UNI_NAM_MAP_NUM, 255, "Unknown")) ? "ERROR" : "OK"); 29 printf("[%s]<Test Case %u> Result: %s!\n", __FUNCTION__, ucTestIndex++, 30 strcmp("0xABCD", NameParser(gUniNameMap, UNI_NAM_MAP_NUM, 0xABCD, NULL)) ? "ERROR" : "OK"); 31 printf("[%s]<Test Case %u> Result: %s!\n", __FUNCTION__, ucTestIndex++, 32 strcmp("NullPoniter", NameParser(NULL, UNI_NAM_MAP_NUM, 0xABCD, NULL)) ? "ERROR" : "OK"); 33 }
gUniNameMap在實際專案中有十餘個條目,若採用邏輯鏈實現將非常冗長。
1.2.5 取值對映
問題:不同模組間同一引數列舉值取值可能有所差異,需要適配。
此處不再給出普通的switch…case或if…else if…else結構,而直接示出以下表驅動實現:
相關推薦
程式碼大全學習筆記之表驅動法
資料壓倒一切。如果選擇了正確的資料結構並把一切組織的井井有條,正確的演算法就不言自明。程式設計的核心是資料結構,而不是演算法。 ——Rob Pike 說明 本文基於這樣的認識:資料是易變的,邏輯是穩定的。 本文例舉的程式設計實現多為程式碼片段,但不
Framework7學習筆記之 表單
選項 href docs action put password ide tor XML 一:表單布局基本格式 表單布局是通過列表來實現的。 <div class="list-block"> <ul> <!--一個表單
代碼大全學習筆記之調試
nag type ges cto 分享 log b2c text ffffff 代碼大全學習筆記之調試
統計學習筆記之K近鄰法
K近鄰作為基本的分類和迴歸方法。在分類中,對新的例項,根據k個最近鄰得訓練例項的類別,通過多數表決進行預測。 一、演算法 輸入:,為例項的特徵向量,為例項的類別。 輸出:例項的的所屬的類y。 (1)根據給定距離度量,在訓練集中找出與最近鄰的k個點,涵蓋這k個點的x
機器學習筆記之梯度下降法
梯度下降法/批量梯度下降法BGD 梯度下降法是一種基於搜尋的最優化方法,即通過不斷地搜尋找到函式的最小值.並不是機器學習專屬的方法.但是在機器學習演算法中求解損失函式的最小值時很常用. 還記得之前說過的機器學習演算法的普遍套路嗎? 定義一個合理的損失函式 優化這個損失函式,求解最小值.
C++學習筆記之泛型算法
vector ace sort clu 算法 clas uniq bits 有時 先貼個代碼 有時間的再補筆記 1 #include<bits/stdc++.h> 2 using namespace std; 3 4 //模板類在接收
算法(第四版)學習筆記之java實現可以動態調整數組大小的棧
length pub move sta gen font -c @override lifo 下壓(LIFO)棧:可以動態調整數組大小的實現 import java.util.Iterator; public class ResizingArrayStack&l
學習筆記之05表格嵌套2(表單)
html 登錄 res cells htm bgcolor www radi charset <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xh
Framework7學習筆記之 操作表(動作組)
nbsp 顯示 span gpo dial 頁面 red text body 一:操作表 操作表其實是一組按鈕,從屏幕底部彈出,點擊不同按鈕執行不同響應函數。 二:舊版:在js文件中定義並顯示操作表 $$(‘選擇器‘).on(‘click‘, functi
梓益C語言學習筆記之常用鏈表操作函數
C語言 鏈表操作 梓益C語言學習筆記之常用鏈表操作函數一、創建鏈表void link_creat_head(STU **p_head,STU *p_new){ STU *p_mov=*p_head; if(*p_head==NULL) //當第一次加入鏈表為空時,head執行p_new { *
梓益C語言學習筆記之鏈表&動態內存&文件
C語言 鏈表 梓益C語言學習筆記之鏈表&動態內存&文件一、定義: 鏈表是一種物理存儲上非連續,通過指針鏈接次序,實現的一種線性存儲結構。二、特點: 鏈表由一系列節點(鏈表中每一個元素稱為節點)組成,節點在運行時動態生成(malloc),每個節點包括兩個部分: 存儲數據元素的數據域 存儲下一個節點地址的
《MySQL 學習筆記》 表操作之子查詢(十)
練習 sts 學習筆記 構建 子查詢 mysql som 運算符 auto SQL語句之使用子查詢 目錄: 構建查詢練習記錄 帶any,some,關鍵字的子查詢 帶all關鍵字的子查詢 帶exists關鍵字的子查詢 帶in關鍵字的子查詢 帶比較運算符
數據結構與算法學習筆記之如何分析一個排序算法?
編號 height href eight 代碼 [] www. 價值 它的 前言 現在IT這塊找工作,不會幾個算法都不好意思出門,排序算法恰巧是其中最簡單的,我接觸的第一個算法就是它,但是你知道怎麽分析一個排序算法麽?有很多時間復雜度相同的排序算法,在實際編碼中,那又如何
數據結構學習筆記之線性表
所有 圖片 指定 表頭 rem 過程 序列 位置 png 一、概念 什麽是線性表呢? 一個簡單的理解如下: 線性表是由稱為元素(Element)的數據項組成的一種有限且有序的序列 其中,這裏有一個需要註意的地方: 有序是指線性表中的每個元素都有自己的位置,而不是指線
數據結構與算法學習筆記之 適合大規模的數據排序
時間復雜度 規模 數組 輸出 數據規模 tmp nlogn lan 情況 前言 在數據排序的算法中,不同數據規模應當使用合適的排序算法才能達到最好的效果,如小規模的數據排序,可以使用冒泡排序、插入排序,選擇排序,他們的時間復雜度都為O(n2),大規模的數據排序就可以使
資料結構學習筆記之線性表
一、概念 什麼是線性表呢? 一個簡單的理解如下: 線性表是由稱為元素(Element)的資料項組成的一種有限且有序的序列 其中,這裡有一個需要注意的地方: 有序是指線性表中的每個元素都有自己的位置,而不是指線性表中的元素按某種順序排列 二、抽象資料型別定義 要給資料結構定
數據結構與算法之美專欄學習筆記-哈希算法(上)
組裝 algorithm 數量 不同的 轉換 完全 負載 結構 快速 哈希算法的定義和原理 將任意長度的二進制串映射為固定長度的二進制串。 這個映射的規則就是哈希算法,而通過原始數據映射之後得到的二進制串就是哈希值。 設計一個優秀的哈希算法需要滿足: 從哈希值不能反向推導
hive程式設計指南學習筆記之二:hive資料庫及其中的表查詢
show databases; /*
程世東老師TensorFlow實戰——個性化推薦,程式碼學習筆記之資料匯入&資料預處理(上)
程式碼來自於知乎:https://zhuanlan.zhihu.com/p/32078473 /程式碼地址https://github.com/chengstone/movie_recommender/blob/master/movie_recommender.ipynb 下一篇有一些資料的
程世東老師TensorFlow實戰——個性化推薦,程式碼學習筆記之資料匯入&資料預處理(下)
這篇主要是進行程式碼中的一些數值視覺化,幫助理解 程式碼來自於知乎:https://zhuanlan.zhihu.com/p/32078473 /程式碼地址https://github.com/chengstone/movie_recommender/blob/master/movie_re