# 2020q3 Quiz 15
contributed by < `Veternal1226` >
###### tags: `sysprog2020`
---
[TOC]
---
[quiz15 原題目連結](https://hackmd.io/@sysprog/2020-quiz15)
---
# 測驗 `1`
LeetCode [835. Image Overlap](https://leetcode.com/problems/image-overlap/) 給定二個影像 A 和 B,二者皆為大小相同的二維正方形矩陣,內部以二進位表示。我們轉換其中一個影像,向左、右、上,或下滑動任意數量的單位,並將其置於另一個影像之上,隨後,該轉換的重疊 (overlap) 指二個影像都有 1 的位置數量,注意:轉換不包含向任一方向旋轉。於是,最大可能的重疊為何?
範例一:
![](https://i.imgur.com/OnF5wBs.png)
範例 1:
- 輸入
$A=\begin{pmatrix}
1 & 1 & 0 \\
0 & 1 & 0 \\
0 & 1 & 0 \\
\end{pmatrix}$
$B=\begin{pmatrix}
0 & 0 & 0 \\
0 & 1 & 1 \\
0 & 1 & 0 \\
\end{pmatrix}$
- 輸出:
- 3
- 解釋
- 將 A 向右移動 1 個單位,然後向下移動 1 個單位
![](https://i.imgur.com/ONuIxjj.png)
- 二張影像中都存在 1 的位置總數為 3,見下圖紅色標記
![](https://i.imgur.com/N00Zxg7.png)
思路:找出所有 1 的位置,對兩個矩陣的所有這些位置進行求差,再統計這些差出現最多的次數。兩個坐標的差表示什麼?即是將其中一個坐標移動到另一個坐標需要移動的向量。於是,在走訪個別單元的過程中,我們找出 A 中所有值為 1 的坐標,移動到 B 中所有值為 1 的坐標需要移動的向量。於是乎,在這些向量中出現次數最多的向量,就是我們要求的整個矩陣應該移動的向量。
以下程式碼針對 LeetCode [835. Image Overlap](https://leetcode.com/problems/image-overlap/),提出可能的解法:
```c=1
#include <string.h>
#define max(a, b) (((a) > (b)) ? (a) : (b))
static inline int overlap_count(unsigned int *AA, unsigned int *BB, int size)
{
int res = 0;
for (int i = 0; i < size; ++i)
res += __builtin_popcount(AA[i] & BB[i]);
return res;
}
static void translate(unsigned int *v,
int vsize,
int i,
int j,
unsigned int *res)
{
memcpy(res, v, sizeof(unsigned int) * vsize);
int mask[32];
for (int k = 0; k < 32; k++)
mask[k] = (1U << k) - 1;
int n = vsize;
if (i >= 0) {
for (int k = 0; k < n; k++)
res[k] >>= i;
} else {
i = -i;
for (int k = 0; k < n; k++)
res[k] = ((res[k] << i) & mask[n]);
}
if (j >= 0) {
ITER1;
for (int k = 0; k < j; k++)
res[k] = 0;
} else {
j = -j;
ITER2;
for (int k = n - j; k < n; k++)
res[k] = 0;
}
}
int largestOverlap(int **img1, int img1Size, int *img1ColSize,
int **img2, int img2Size, int *img2ColSize)
{
int n = img1Size;
unsigned int A[n], B[n];
memset(A, 0, sizeof(unsigned int) * n);
memset(B, 0, sizeof(unsigned int) * n);
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
if (img1[i][j] == 1)
A[i] |= 1 << j;
if (img2[i][j] == 1)
B[i] |= 1 << j;
}
}
int res = 0;
for (int i = -(n - 1); i <= (n - 1); ++i) {
for (int j = -(n - 1); j <= (n - 1); ++j) {
unsigned int AA[n];
translate(A, n, i, j, AA);
res = max(res, overlap_count(AA, B, n));
}
}
return res;
}
```
請補完程式碼,使其符合 LeetCode 題目要求。
參考資料:
- [LeetCode 835. Image Overlap](https://www.cnblogs.com/grandyang/p/10355589.html)
作答區
ITER1 = (c\)
`(a)` `for (int k = 0; k < n - j; k++) res[k + j] = res[k]`
`(b)` `for (int k = 0; k <= n - j; k++) res[k + j] = res[k]`
`(c)` `for (int k = n - j - 1; k >= 0; k--) res[k + j] = res[k]`
`(d)` `for (int k = n - j; k >= 0; k--) res[k + j] = res[k]`
ITER2 = (b)
`(a)` `for (int k = j; k < n; k++) res[k + j] = res[k]`
`(b)` `for (int k = j; k < n; k++) res[k - j] = res[k]`
`(c)` `for (int k = j - 1; k < n; k++) res[k + j] = res[k]`
`(d)` `for (int k = j - 1; k < n; k++) res[k - j] = res[k]`
## 程式碼運作
:::success
延伸題目:
1. 解釋上述程式碼運作原理
:::
- int largestOverlap()
47~50 行為初始化
51~58 行將原本題目提供的 bitmap 轉成一維的陣列,將 bitmap 以 int 格式儲存
60~67 行測試所有平移結果,找出重疊的最大值
64 行的 `translate()` 將原本的陣列做平移
65 行的 `overlap_count()` 用來算出重疊量,並檢查是否需更新最大值
- static void translate()
21 行的 `mask` 用來取出右移後真正位於比較空間內的 bits,比較空間外的會被 `mask` 清為 0
23~30 行進行左右平移 (x軸)
32~41 行進行上下平移 (y軸)
y軸範圍參考此圖
`j >= 0` :
![](https://i.imgur.com/xPoEkVA.png)
因此在 `j >= 0` 的情況下
範圍為 (n-1-j) ~ 0
搬移方式為 res[k + j] = res[k]
所以 ITER1 = ==`(c)` `for (int k = n - j - 1; k >= 0; k--) res[k + j] = res[k]`==
`j < 0` :
![](https://i.imgur.com/uflctIy.png)
因此在 `j < 0` 的情況下
範圍為 (-j) ~ (n-1)
搬移方式為 res[k + j] = res[k]
但因題目先將 `j = -j`
因此範圍改為 j ~ (n-1)
搬移方式為 res[k - j] = res[k]
所以 ITER2 = ==`(b)` `for (int k = j; k < n; k++) res[k - j] = res[k]`==
- static inline int overlap_count()
7~8 行用於計算兩圖重疊量,先進行 AND 疊合,在使用 `popcount()` 方式計算 `1` 的數量,計算行數與原矩陣 _大小相同_ 。 (此處即為可優化的部分)
## 優化
:::success
延伸題目:
2. 提出效率更高,且空間佔用更少的 C 程式碼實作
:::
主要更改處為取消 34、35、39、40 行的補0行為,把 37 行的 -j 改成 j 方便理解,並將 overlap_count 的比較範圍縮小。
舉例圖解:
>藍色陣列為平移前,紅色陣列為平移後,綠框表有興趣之區域 ( 搬移基準點或比較範圍 )。
以 `n = 8, i = 2, j = 2` 為例:
搬移過程:
搬移順序為從 y = n-j-1 行開始至 y= 0 (方向往上),往右移2單位、下移2單位
![](https://i.imgur.com/xPoEkVA.png)
比較範圍:
![](https://i.imgur.com/V8poOZz.png)
優化後比較範圍:
![](https://i.imgur.com/hGYca7t.png)
以 n=8, i=-2, j=-2 為例:
搬移過程:
搬移順序為從 y = 0-j (同義於 -j ) 行開始至 y= n-1 (方向往下),往左移2單位、上移2單位
![](https://i.imgur.com/uflctIy.png)
比較範圍:
![](https://i.imgur.com/Z4mtaAA.png)
優化後比較範圍:
![](https://i.imgur.com/Cw9XIaC.png)
原本方法記憶體占用:
![](https://i.imgur.com/dlEsSjT.png)
優化比較範圍後記憶體占用:
![](https://i.imgur.com/bdmNbR3.png)
優化後程式碼:
```c=1
#include <string.h>
#define max(a, b) (((a) > (b)) ? (a) : (b))
static inline int overlap_count(unsigned int *AA, unsigned int *BB, int size, int j)
{
int res = 0;
if(j>=0){
for (int k = j; k < size; ++k)
res += __builtin_popcount(AA[k] & BB[k]);
}
else{
for (int k = 0; k < size+j; ++k)
res += __builtin_popcount(AA[k] & BB[k]);
}
return res;
}
static void translate(unsigned int *v,
int vsize,
int i,
int j,
unsigned int *res)
{
memcpy(res, v, sizeof(unsigned int) * vsize);
int mask[32];
for (int k = 0; k < 32; k++)
mask[k] = (1U << k) - 1;
int n = vsize;
if (i >= 0) {
for (int k = 0; k < n; k++)
res[k] >>= i;
} else {
i = -i;
for (int k = 0; k < n; k++)
res[k] = ((res[k] << i) & mask[n]);
}
if (j >= 0) {
for (int k = n - j - 1; k >= 0; k--)
res[k + j] = res[k];
} else {
for (int k = -j ; k < n; k++)
res[k + j] = res[k];
}
}
int largestOverlap(int **img1, int img1Size, int *img1ColSize,
int **img2, int img2Size, int *img2ColSize)
{
int n = img1Size;
unsigned int A[n], B[n];
memset(A, 0, sizeof(unsigned int) * n);
memset(B, 0, sizeof(unsigned int) * n);
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
if (img1[i][j] == 1)
A[i] |= 1 << j;
if (img2[i][j] == 1)
B[i] |= 1 << j;
}
}
int res = 0;
for (int i = -(n - 1); i <= (n - 1); ++i) {
for (int j = -(n - 1); j <= (n - 1); ++j) {
unsigned int AA[n];
translate(A, n, i, j, AA);
res = max(res, overlap_count(AA, B, n, j));
}
}
return res;
}
```
---
# 測驗`2`
LeetCode [1569. Number of Ways to Reorder Array to Get Same BST](https://leetcode.com/problems/number-of-ways-to-reorder-array-to-get-same-bst/) 給定一個陣列 nums 表示 $1$ 到 $n$ 的排列,依據元素在 `nums` 中的順序,依次插入一個初始為空的二元搜尋樹 (BST)。請你統計將 `nums` 重新排序後,滿足以下條件的個數:
- 重排後得到的 BST 與 nums 原本數值順序得到的 BST 相同。
例如,給定
$nums=[2,1,3]$,我們得到一棵 `2` 為 root、`1` 為 left 及 `3` 為 right 的樹。
![](https://i.imgur.com/zp2bTKD.png)
陣列 $[2,3,1]$ 也能得到相同的 BST,但 $[3,2,1]$ 會得到一棵不同的 BST 。
由於答案可能會很大,請將結果對 $10^9+7$ 取餘數。
- 為何是 $10^9+7$ 呢?請見 [Modulo 10^9+7 (1000000007)](https://www.geeksforgeeks.org/modulo-1097-1000000007/)
範例 1
![](https://i.imgur.com/zp2bTKD.png)
- 輸入
- $nums=[2,1,3]$
- 輸出
- `1`
- 解釋
- 我們將 `nums` 重排,$[2,3,1]$ 可得到相同的 BST
- 沒有其他得到相同 BST 的方法
範例 2
![](https://i.imgur.com/EQRaUTp.png)
- 輸入
- $nums=[3,4,5,1,2]$
- 輸出
- `5`
- 解釋
- 下面 5 個陣列可得到相同的 BST
- $[3,1,2,4,5]$
- $[3,1,4,2,5]$
- $[3,1,4,5,2]$
- $[3,4,1,2,5]$
- $[3,4,1,5,2]$
範例 3
![](https://i.imgur.com/pHEpvcY.png)
- 輸入
- $nums=[1,2,3]$
- 輸出
- `0`
- 解釋
- 沒有其他排列順序能得到相同的 BST
範例 4
![](https://i.imgur.com/KnFWUNa.png)
- 輸入
- $nums=[3,1,2,5,4,6]$
- 輸出
- `19`
範例 5
- 輸入
- $nums=[9,4,2,1,3,6,5,7,8,14,11,10,12,13,16,15,17,18]$
- 輸出
- `216212978`
- 解釋
- 得到相同 BST 的個數是 `3216212999`
- 將其對 $10^9+7$ 取餘後得到 `216212978`
根節點是陣列第一個數,分為左右兩個子樹,左右子樹之間的順序若不變即可
假設左子樹 $L$ 長度 $nL$,右子樹 $R$ 長度 $nR$,則有效組合數為 $C^{nL}_{nL+nR}×f(L)×f(R)$
```c=1
#include <string.h>
static const int MOD = 1e9 + 7; /* prime */
static int recursive(int *nums, int nums_size, int N, DECL)
{
if (nums_size <= 2)
return 1;
int left[N + 1], left_size = 0, right[N + 1], right_size = 0;
for (int i = 1; i < nums_size; i++)
nums[i] > nums[0] ? (right[right_size++] = nums[i])
: (left[left_size++] = nums[i]);
int l = recursive(left, left_size, N, cache),
r = recursive(right, right_size, N, cache);
return (((long) cache[nums_size - 1][left_size] * l % MOD) * r) % MOD;
}
int numOfWays(int *nums, int N)
{
int comb[N + 1][N + 1]; /* combination table */
memset(comb, 0, sizeof(comb));
comb[0][0] = 1;
for (int i = 1; i <= N; i++) {
comb[i][0] = 1;
for (int j = 1; j <= N; j++)
INIT;
}
return recursive(nums, N, N, comb) - 1;
}
```
DECL = (d)
`(a)` `int cache[N][N]`
`(b)` `int cache[N - 1][N - 1]`
`(c)` `int cache[N + 1][N]`
`(d)` `int (*cache)[N + 1]`
`(e)` `int (*cache)[N]`
INIT = (a)
`(a)` `comb[i][j] = (comb[i - 1][j] + comb[i - 1][j - 1]) % MOD`
`(b)` `comb[i][j] = (comb[i - 1][j] + comb[i - 1][j - 1])`
`(c)` `comb[i][j] = (comb[i][j] + comb[i - 1][j - 1]) % MOD`
`(d)` `comb[i][j] = (comb[i][j] + comb[i - 1][j - 1])`
`(e)` `comb[i][j] = (comb[i][j - 1] + comb[i - 1][j - 1]) % MOD`
`(f)` `comb[i][j] = (comb[i][j - 1] + comb[i - 1][j - 1])`
## 解題想法
從提示與[花花醬](https://zxi.mytechroad.com/blog/recursion/leetcode-1569-number-of-ways-to-reorder-array-to-get-same-bst/)的教學中,可了解此題的解題關鍵為:
>根節點是陣列第一個數,分為左右兩個子樹,左右子樹之間的順序若不變即可
>假設左子樹 $L$ 長度 $nL$,右子樹 $R$ 長度 $nR$,則有效組合數為 $C^{nL}_{nL+nR}×f(L)×f(R)$
題目所挖空的部分為取組合的部分 ($C^{nL}_{nL+nR}$)
目的為先把組合的結果算出並建好表格,使每次取組合時不用重算
## 程式碼運作
:::success
延伸題目:
1. 解釋上述程式碼運作原理
:::
- int numOfWays()
23~28 行: 建立組合表,之後可直接查表,可以不用重複計算相同的組合結果
```c=23
comb[0][0] = 1;
for (int i = 1; i <= N; i++) {
comb[i][0] = 1;
for (int j = 1; j <= N; j++)
INIT;
}
```
根據花花醬的教學,此表格是算出 pascal's triangle 中各元素的值。
![](https://i.imgur.com/Os6N47q.png)
因此 INIT 應選 ==(a) comb[i][j] = (comb[i - 1][j] + comb[i - 1][j - 1]) % MOD==
29 行: 進入 recursive() 遞迴函式,進入遞迴時,將組合表表一起傳入,從 20 行可看出組合表的大小為 [N+1][N+1]
DECL選項中只有 ==`(d)` `int (*cache)[N + 1]`== 符合,故選 (d)
- static int recursive()
7~8 行: 終止條件:`size<=2`
9 行: 初始化
10~12 行: 將大於當前 nums[0] 的值放入右子樹,將小於 nums[0] 的值放入左子樹
13~14 行: 進入遞迴,回傳結果為子樹可能的組合數
15 行: 回傳值應為 $C^{nL}_{nL+nR}×f(L)×f(R)$,組合數使用查表方式得到值;為了避免相乘結果數值太大,每次相乘均需對 $10^9+7$ 取餘數。
>對$10^9+7$取餘數原因:
hash成本太高,不想太複雜操作 (需考慮碰撞、hash function 等)
單純取餘數會有不夠分散的問題 (ex: 360%2=0,結果集中在小數值不夠分散)
因此採用對大質數取餘數的方式實作
## 優化
:::success
延伸題目:
2. 提出效率更高,且空間佔用更少的 C 程式碼實作
:::
從花花醬的教學中得知建立組合表方式是算出 pascal's triangle 中各元素的值,而 pascal's triangle 並不會用到右上方的三角形。
comb在矩陣中的儲存方式:
![](https://i.imgur.com/LuwWLvk.png)
右上角紅色三角形表示不會使用到的位置
![](https://i.imgur.com/DNgqix4.png)
原本建表過程中的第 26 行如下
```c=26
for (int j = 1; j <= N; j++)
```
可看出其計算了整張表的值,將 j 的範圍改成 `j <= i` 能夠節省計算右上三角形的值花費的計算成本。實際改動如下列程式碼。
```c=26
for (int j = 1; j <= i; j++)
```
優化前執行時間:
![](https://i.imgur.com/TbgbJ39.png)
![](https://i.imgur.com/xM7pmuC.png)
優化後執行時間:
![](https://i.imgur.com/VULgqZu.png)
![](https://i.imgur.com/iF7HFnM.png)
---
# 測驗`3`
在 Ubuntu/Debian Linux 中,可執行 sudo apt install dictionaries-common 以安裝辭典套件,於是可得到 /usr/share/dict/words 檔案,後者包含超過 10 萬個單字/詞:
```
$ wc -l /usr/share/dict/words
```
考慮以下 C 程式 (strsearch.c) 可從給定的詞典中檢索:
```c=1
/*
* Hash table is represented as `htable` array of size N (N = number of lines in
* dict), where each element either points to the end of a singly-linked list or
* is equal to zero. Lists are stored in pre-allocated array `clist` of size N.
*
* This implementation is memory-efficient and cache-friendly, requiring only
* 12N + O(1) bytes of "real" memory which can be smaller than the size of
* dict, sacrificing however for request processing speed, which is
* O(req_size) in most cases, but may be up to O(dict_size) due to hash collision.
*/
#include <fcntl.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
/* 2 additional bytes for '\n' and '\0' */
#define MAX_REQUEST_SIZE (128 * 1024 * 1024 + 2)
struct htable_entry {
uint32_t ptr;
};
struct collision_list {
uint32_t fpos;
uint32_t len;
uint32_t prev;
};
struct compr_collision_list {
uint32_t fpos;
uint32_t len;
};
typedef struct htable_entry htable_entry_t;
typedef struct collision_list collision_list_t;
typedef struct compr_collision_list compr_collision_list_t;
static char *dict;
static htable_entry_t *htable;
static collision_list_t *clist;
static compr_collision_list_t *cclist;
static size_t num_of_bytes, num_of_lines;
static size_t num_of_buckets;
static int clist_size = 1;
static uint32_t hash(char *s, size_t len)
{
uint32_t hash = 5381;
for (size_t i = 0; i < len; i++)
hash = ((hash << 5) + hash) + (s[i] - 32);
return hash % num_of_buckets;
}
static void htable_insert(size_t fpos, size_t len)
{
uint32_t h = hash(dict + fpos, len);
clist[clist_size].fpos = fpos;
clist[clist_size].len = len;
clist[clist_size].prev = htable[h].ptr;
htable[h].ptr = clist_size;
++clist_size;
}
static bool htable_lookup(char *s, size_t len)
{
uint32_t h = hash(s, len);
uint32_t ptr = htable[h].ptr;
uint32_t nextptr = htable[h + 1].ptr;
for (; ptr < nextptr; ptr++) {
if ((len == cclist[ptr].len) &&
!memcmp(s, dict + cclist[ptr].fpos, len))
return true;
}
return false;
}
int main(int argc, char **argv)
{
if (argc != 2) {
printf("usage: %s dictionary_file\n", argv[0]);
return 2;
}
/* Map dictionary file directly to memory for easier access from program */
int dictfd;
if ((dictfd = open(argv[1], O_RDONLY)) < 0)
return 1;
struct stat st;
fstat(dictfd, &st);
num_of_bytes = st.st_size;
if ((dict = mmap(0, num_of_bytes, PROT_READ, MAP_PRIVATE, dictfd, 0)) ==
MAP_FAILED)
return 1;
/* Count number of lines in file */
for (size_t i = 0; i < num_of_bytes; i++) {
if (dict[i] == '\n')
num_of_lines++;
}
num_of_lines++;
num_of_buckets = num_of_lines;
htable = calloc(num_of_buckets + 1, sizeof(htable_entry_t));
clist = malloc((num_of_lines + 1) * sizeof(collision_list_t));
size_t prev_start = 0;
for (size_t i = 0; i < num_of_bytes; i++) {
if (dict[i] == '\n') {
htable_insert(prev_start, P1);
prev_start = P2;
}
}
/* Compress collision list and update ptrs in htable accordingly */
htable[num_of_buckets].ptr = num_of_lines;
cclist = malloc((num_of_lines + 1) * sizeof(compr_collision_list_t));
size_t clines = 0;
for (size_t i = 0; i < num_of_buckets; i++) {
uint32_t ptr = htable[i].ptr;
htable[i].ptr = clines;
while (ptr) {
cclist[clines].fpos = clist[ptr].fpos;
cclist[clines].len = clist[ptr].len;
ptr = clist[ptr].prev;
clines++;
}
}
free(clist);
/* Ready to accept requests */
char *req_buf = malloc(MAX_REQUEST_SIZE);
while (!feof(stdin)) {
fgets(req_buf, MAX_REQUEST_SIZE, stdin);
size_t len = strlen(req_buf);
/* Exit on "exit" command */
if (!strncmp(req_buf, "exit\n", len))
break;
printf("%s: %s\n", req_buf, htable_lookup(req_buf, len) ? "YES" : "NO");
}
free(req_buf);
free(htable);
free(cclist);
munmap(dict, num_of_bytes);
close(dictfd);
return 0;
}
```
給定測試輸入 (檔名: inputs) 如下:
```
gnu
linux
kernel
X
XX
exit
```
測試方式:
```
$ ./strsearch /usr/share/dict/words < inputs
```
預期執行結果:
```
gnu
: YES
linux
: NO
kernel
: YES
X
: YES
XX
: NO
```
請補完程式碼。
P1 = (d)
`(a)` `prev_start + i`
`(b)` `prev_start + i + 1`
`(c)` `i - prev_start`
`(d)` `i - prev_start + 1`
P2 = (b)
`(a)` `i`
`(b)` `i + 1`
`(c)` `i - 1`
## 解題想法
```c=113
size_t prev_start = 0;
for (size_t i = 0; i < num_of_bytes; i++) {
if (dict[i] == '\n') {
htable_insert(prev_start, P1);
prev_start = P2;
}
}
```
```c=60
static void htable_insert(size_t fpos, size_t len)
{
uint32_t h = hash(dict + fpos, len);
clist[clist_size].fpos = fpos;
clist[clist_size].len = len;
clist[clist_size].prev = htable[h].ptr;
htable[h].ptr = clist_size;
++clist_size;
}
```
可從這兩段程式碼中得知
116行的行為為: 將起始點 prev_start 與長度 P1 傳入 htable_insert
117行的行為為: 更新起始點 prev_start
圖解:
![](https://i.imgur.com/8g1QGlt.png)
將起始位址 `prev_start - 0` 與長度 `i - prev_start +1` 作為參數輸入 `htable_insert` 中,再將 `prev_start` 更新到 `i+1` 的位址,所以題目要求的程式碼應如下:
```c=1
htable_insert(prev_start, i - prev_start + 1);
prev_start = i + 1;
```
因此,P1應選 ==`(d)` `i - prev_start + 1`== , P2應選 ==`(b)` `i + 1`== 。
## 程式碼運作原理
:::success
延伸題目:
1. 解釋上述程式碼運作原理
:::
- static uint32_t hash()
使用 DJB Hash Function
回傳值為 hash值 除以 詞的總數量
參考來源:
>1. [algorithm - Reason for 5381 number in DJB hash function? - Stack Overflow](https://stackoverflow.com/questions/10696223/reason-for-5381-number-in-djb-hash-function)
>2. [hash](http://www.cse.yorku.ca/~oz/hash.html)
- static void htable_insert()
將新的詞輸入至 htable 與 clist 中以方便查詢與比較
62 行: 取得 hash 值
63 行: 記錄該詞的開頭位置,放入 clist.fpos
64 行: 計算該詞的長度放入 clist.len
65 行: 記錄 htable 中該詞位置的原有指標,使用 link list 的方式處理碰撞
66 行: 更新 htable 該詞位置上的 list 長度
- static bool htable_lookup()
查詢 htable 中是否存在輸入的詞
72 行: 取得 hash 值
73 行: 取得 htable 中記錄的指標
74 行: 取得 htable 中下個記錄的指標
75~80 行: 查詢 cclist 中是否有輸入的詞出現,並回傳結果
- int main()
85~88 行: 檢查參數格式是否正確
91~93 行: 開啟 `argv[1]` 代表的檔案以作為辭典檔輸入
95~100 行: 將開啟檔案的 file descriptor轉成 stat 格式,利用 stat 資訊使用 `mmap()` 將辭典檔映射到記憶體,把檔案操作變成記憶體操作。
>使用mmap原因: I/O最佳化
>把檔案操作變成記憶體操作
>檔案操作 (fopen): 詞典檔案太大時啟動時要讀很久,但只需開啟檔案一次。
>記憶體操作 (mmap): 對應到記憶體,發生page fault時才需要讀取成本,但讀取次數隨 page fault 次數增加。
103~107 行: 計算辭典檔行數
109~111 行: 宣告空間放置 htable 與 clist
113~119 行: 利用 `\n` 做斷詞依據,將每個字放入 htable 中,放入 htable 位置的依據為 hash function (此題使用 DJB Hash Function)
122~134 行: 使用 collision list方式處理碰撞
138 行: 初始化 fgetd 需要的 buffer 空間
140~147 行: 從標準輸入查詢各字串段是否位於 hash function 中
141 行: 從標準輸入輸入至 req_buf 中
142 行: 計算輸入的字串長度
144~146 行: 查詢 htable 內能不能查詢到輸入的詞
附註:
hash 存在的原因是為了減少重複查詢的查詢成本。
有驗證過在 ubuntu 20 環境下此程式可正確執行。
![](https://i.imgur.com/fm3my7W.png)
:::success
延伸題目:
2. 提出效率更高,且空間佔用更少的 C 程式碼實作
:::
//TODO