# 2020q3 Homework4 (quiz4)
contributed by < `tsengsam` >
[第四週測驗題](https://hackmd.io/@sysprog/2020-quiz4)
---
## 測驗 `1`: Hamming Distance
#### 計算兩數在二進位表示時有幾個位元不同
* 我需要記錄兩數在相對應的位元數值不同的數量:根據題目使用 `popcount` 算 set bit 數量可以推測兩個不同的位元經過 `OP` 計算後應為 `1`,而相對應的運算子便為 `XOR`,本題選 `(c)`。
```cpp=
int hammingDistance(int x, int y) {
return __builtin_popcount(x ^ y);
}
```
---
## 測驗 `2`: LeetCode 1483. Kth Ancestor of a Tree Node
#### 給定一棵 complete binary tree 並從根節點編號 $0$ 自 $n-1$,且已知parent\[i] 是節點 i 的父節點,欲設計能回傳第 k 個祖先節點的函式。
* 樹的資料結構:
```cpp
typedef struct {
AAA;
int max_level;
} TreeAncestor;
```
- 由下面的程式可知 `AAA` 應為儲存指標的指標,故 `AAA` 為 `int **parent`
- 而 `max_level` 記錄著該樹的 level,若只有一個節點 level 為 1
* 原本我以為這題的設計是將每個節點 $i$ 的所有祖先節點 $j$ 都存到 `obj->parent[i][j]`,但當看到 `treeAncestorGetKthAncestor` 函式並不是直接 access `obj->parent[i][j]`,而是以 2 的次方個祖先節點向上尋找,才發現 `obj->parent[i][j]` 存的是第 $2^j$ 個的祖父節點,
* `treeAncestorCreate` 用來建立能記錄 $i$ 節點第 $2^j$ 個祖先節點的樹
```cpp=
TreeAncestor *treeAncestorCreate(int n, int *parent, int parentSize)
{
TreeAncestor *obj = malloc(sizeof(TreeAncestor));
obj->parent = malloc(n * sizeof(int *));
int max_level = 32 - __builtin_clz(n) + 1;
for (int i = 0; i < n; i++) {
obj->parent[i] = malloc(max_level * sizeof(int));
for (int j = 0; j < max_level; j++)
obj->parent[i][j] = -1;
}
for (int i = 0; i < parentSize; i++)
obj->parent[i][0] = parent[i];
for (int j = 1;; j++) {
int quit = 1;
for (int i = 0; i < parentSize; i++) {
obj->parent[i][j] = obj->parent[i][j + BBB] == -1
? -1
: obj->parent[obj->parent[i][j - 1]][j - 1];
if (obj->parent[i][j] != -1) quit = 0;
}
if (quit) break;
}
obj->max_level = max_level - 1;
return obj;
}
```
- Level $l$ 的節點 $x$ 編號滿足 $2^{l-1} \le x \le 2^l - 1$,若將編號用二進制表示可知其 level 由最高的 set bit 決定,因此可透過先前學過的 `__builtin_clz(n)` 來協助計算 set bit 位置。
- Line 7 開始的兩層迴圈在初始化 `obj->parent[i][j]`,表示節點 $i$ 的第 $2^j$ 個祖先節點,若祖先節點不存在值為 `-1`。
- Line 12 是因為接下來使用 Dynamic programming (DP) 技巧故先初始化 `parent[i][0]` 為 $i$ 的第 $2^0$ 個也就是父節點。
- Line 15-24 為用來計算祖先節點的主要迴圈,使用 DP 的技巧從根節點開始逐漸將各節點 $i$ 的祖先節點們記錄到 `parent[i][j]`,Line 18 的三元運算子在計算節點 $i$ 的 第 $2^j$ 個祖先節點,原理是如果 連 $2^{j-1}$ 個祖先不存在的那在更老的祖先 $2^j$ 便不可能存在;若存在則可透過第 $2^{j-1}$ 個祖先的 第 $2^{j-1}$ 個祖先來取得,故 `BBB` 為 `(-1)`。
* `treeAncestorGetKthAncestor` 用來取得第 `k` 個祖先節點的編號
```cpp=
int treeAncestorGetKthAncestor(TreeAncestor *obj, int node, int k)
{
int max_level = obj->max_level;
for (int i = 0; i < max_level && node != -1; ++i)
if (k & (CCC))
node = obj->parent[node][i];
return node;
}
```
之所以 `obj` 能夠只存 2 的次方的祖先節點,是因為任意 `k` 皆能由 2 的次方的數相加組成。譬如 $k = 11 = 2^0 + 2^1 + 2^3$ 時,我可以先找到 i 的第 $2^0$ 個祖先,再從該祖先繼續向上找其第 $2^1$ 個的祖先,最後再找第 $2^3$ 個祖先便能找到第 $k = 11$ 個的祖先了。
`CCC` 確認 `k` 的 set bit,也就是用來確認 2 的多少次方是否為組成 `k` 的數,故為 `1 << i`。
* `treeAncestorFree` 釋放記憶體空間
---
## 測驗 `3`: FizzBuzz
#### 若輸入為 3 個倍數,輸出 "Fizz";若為 5 的倍數輸出 "Buzz", 若為 15 倍數則輸出 "FizzBuzz"
* 有別於直觀地用 if 來窮舉所有情況,使用 branchless 的方式來完成
* `is_divisible` 的作法可參考 [quiz2](https://hackmd.io/MxBHGC17QA-h2KXL1KC_nA?view#%E3%80%90%E6%B8%AC%E9%A9%97-4%E3%80%91) 測驗 4
```cpp=
#define MSG_LEN 8
static inline bool is_divisible(uint32_t n, uint64_t M) {
return n * M <= M - 1;
}
static uint64_t M3 = UINT64_C(0xFFFFFFFFFFFFFFFF) / 3 + 1;
static uint64_t M5 = UINT64_C(0xFFFFFFFFFFFFFFFF) / 5 + 1;
int main(int argc, char *argv[]) {
for (size_t i = 1; i <= 100; i++) {
uint8_t div3 = is_divisible(i, M3);
uint8_t div5 = is_divisible(i, M5);
unsigned int length = (2 << div3) << div5;
char fmt[MSG_LEN + 1];
strncpy(fmt, &"FizzBuzz%u"[(MSG_LEN >> KK1) >> (KK2 << KK3)], length);
fmt[length] = '\0';
printf(fmt, i);
printf("\n");
}
return 0;
}
```
* Line 17 第2個參數在判斷當輸入為 3 或 15 的倍數時起始位置為 index 0,若為 5 的倍數時起始位置則在 index 4,因為 `MSG_LEN` 由 8 開始遞減,故先判斷有沒有 Buzz,即 `KK1` 為 `div5`,而若是 3 或 15 倍數時要將 `MSG_LEN` 降至 0 的選項就是 `KK2` 為 `div3`、`KK3` 為 `2` 。
## 測驗 `4`:
#### 將原本含除法 (`/`) 的運算改成能預先處理的程式
* 預先處理的函式為 `__builtin_ffs(x)`,會回傳最低的 1 位元的 index 加 1
* 本提要求的 `divident` 至少包含哪些數字即為 `divident >> ffs32(divident)`,因為已有給定的數值分佈,而任何 2 的次方的數皆會得到 1,其餘則會變成3, 5, 7
###### tags: `linux2020`