# 2024q1 Homework2 (quiz1+2)
contributed by < [`hugo0406`](https://github.com/hugo0406) >
## [第一周測驗題](https://hackmd.io/@sysprog/linux2024-quiz1)
### 測驗一
### 測驗二
---
## [第二周測驗題](https://hackmd.io/@sysprog/linux2024-quiz2)
### 測驗一
:::warning
:warning: 注意以下:
1. 避免過早逐行程式碼講解,否則很容易淪落為「舉燭」。
2. 程式碼背後的動機、考量因素,還有手法比程式碼本身,更值得在開發紀錄探討。
3. 為何給定 preorder 和 inorder,即可重建二元樹?你該探討。
:::
給定由二個由整數構成陣列的前序 (preorder) 和中序 (inorder) 形式,分別是對同一棵二元樹的前序及中序走訪結果,藉此重建此二元樹。以下是運用 Linux 核心的 hash table 實作程式碼
:::danger
不用抄寫題目,專注探討程式原理、闡述你的洞見,還有你預期做哪些改進。
:::
hlist 用於 hash table 的實作,詳細內容請參考[Linux 核心的 hash table 實作](https://hackmd.io/@sysprog/linux-hashtable)。
hlist_head 和 hlist_node 用於 hash table 中 bucket 的實作,具有相同 hash value 的節點 (即 $hash(i) = hash(j),i\neq j$ ) 會放在同一條鏈結串列上。
值得我們注意的是 hlist_node 裡的 `**pprev` 是指標的指標,它指向的是前一個節點的 next 指標。 這樣的目的是即使要刪除的節點是 **最前面的節點** 時,也可以透過 `*pprev = next` 去直接修改指標的指向。
```c
struct order_node {
struct hlist_node node;
int val;
int idx;
};
```
`order_node` 結構如下圖所示:
```graphviz
digraph {
label = "order_node"
rankdir = LR;
node[shape="Mrecord"];
node_1[label = "<v>val|<i>index|<h>hlist_head |{<p>pprev | <n>next}"];
}
```
```c
struct TreeNode {
int val;
struct TreeNode *left, *right;
};
```
`TreeNode`結構如下圖所示:
```graphviz
digraph {
label = "TreeNode"
rankdir = LR;
node[shape="Mrecord"];
node_1[label = "<m>val | {<p> left | <n>right}", group = list];
}
```
```c
static inline void INIT_HLIST_HEAD(struct hlist_head *h)
{
h->first = NULL;
}
```
`INIT_HLIST_HEAD` 對鏈結串列的頭初始化為 NULL ,對應的圖如下:
```graphviz
digraph G {
rankdir = LR;
splines = false;
node[shape = "record"]
hlist_head[label = "<m>hlist_head | <n>first"]
NULL[shape = plaintext, label = "NULL", group = list]
hlist_head:n -> NULL
}
```
```c
static inline void hlist_add_head(struct hlist_node *n, struct hlist_head *h)
{
if (h->first)
h->first->pprev = &n->next;
n->next = AAAA;
n->pprev = &h->first;
h->first = n;
}
```
從函式名稱 `hlist_add_head` 不難看出要在鏈結串列的開頭加入節點 ( LIFO 準則)
首先可以看到一開始的 if 為判斷 `h->first` 是否為 NULL,若不為 NULL 則代表鏈結串列上有節點。
- 鏈結串列非空
```graphviz
digraph G {
rankdir = LR;
splines = false;
node[shape = "record"]
hlist_head[label = "<m>hlist_head | <n>first"]
node_1[label = "<m>hlist_node_1 | {<p>pprev | <n>next}", group = list];
node_2[label = "<m>hlist_node_2 | {<p>pprev | <n>next}", group = list];
node_n[label = "<m>hlist_node_n | {<p>pprev | <n>next}", group = list];
NULL[shape = plaintext, label = "NULL", group = list]
{rank = "min" hlist_head}
hlist_head -> node_1 -> node_2 [
weight = 10, style = invis
]
hlist_head:n -> node_1:m;
node_1:p -> hlist_head:n;
node_1:n -> node_2:m;
node_2:p -> node_1:n;
node_2:n -> NULL;
}
```
```c
#4 h->first->pprev = &n->next;
```
```graphviz
digraph G {
rankdir = LR;
splines = false;
node[shape = "record"]
hlist_head[label = "<m>hlist_head | <n>first"]
node_n[label = "<m>hlist_node_n | {<p>pprev | <n>next}", group = list];
node_1[label = "<m>hlist_node_1 | {<p>pprev | <n>next}", group = list];
node_2[label = "<m>hlist_node_2 | {<p>pprev | <n>next}", group = list];
NULL1[shape = plaintext, label = "NULL", group = list]
{rank = "min" hlist_head}
hlist_head -> node_1 -> node_2 [
weight = 10, style = invis
]
hlist_head:n -> node_1:m;
node_1:n -> node_2:m;
node_2:p -> node_1:n;
node_2:n -> NULL1;
node_1:p -> node_n:n[color=red];
{rank = same; hlist_head;node_n};
}
```
```c
#5 n->next = h->first; //AAAA
```
```graphviz
digraph G {
rankdir = LR;
splines = false;
node[shape = "record"]
hlist_head[label = "<m>hlist_head | <n>first"]
node_1[label = "<m>hlist_node_1 | {<p>pprev | <n>next}", group = list];
node_2[label = "<m>hlist_node_2 | {<p>pprev | <n>next}", group = list];
node_n[label = "<m>hlist_node_n | {<p>pprev | <n>next}", group = list];
NULL1[shape = plaintext, label = "NULL", group = list]
{rank = "min" hlist_head}
hlist_head -> node_1 -> node_2 [
weight = 10, style = invis
]
hlist_head:n -> node_1:m;
node_1:n -> node_2:m;
node_2:p -> node_1:n;
node_2:n -> NULL1;
node_1:p -> node_n:n;
{rank = same; hlist_head;node_n};
node_n:n -> node_1:m[color=red];
}
```
```c
#6 n->pprev = &h->first;
```
```graphviz
digraph G {
rankdir = LR;
//splines = false;
node[shape = "record"]
hlist_head[label = "<m>hlist_head | <n>first"]
node_n[label = "<m>hlist_node_n | {<p>pprev | <n>next}", group = list];
node_1[label = "<m>hlist_node_1 | {<p>pprev | <n>next}", group = list];
node_2[label = "<m>hlist_node_2 | {<p>pprev | <n>next}", group = list];
NULL1[shape = plaintext, label = "NULL", group = list]
{rank = "min" hlist_head}
hlist_head -> node_1 -> node_2 [
weight = 10, style = invis
]
hlist_head -> node_n [style = invis]
node_n -> hlist_head [style = invis]
hlist_head:n -> node_1:m;
node_1:n -> node_2:m;
node_2:p -> node_1:n;
node_2:n -> NULL1;
node_1:p -> node_n:n;
{rank=same;node_1;}
node_n:n -> node_1:m;
node_n:p -> hlist_head:n[color=red];
}
```
```c
#7 h->first = n;
```
```graphviz
digraph G {
rankdir = LR;
splines = false;
node[shape = "record"]
hlist_head[label = "<m>hlist_head | <n>first"]
node_n[label = "<m>hlist_node_n | {<p>pprev | <n>next}", group = list];
node_1[label = "<m>hlist_node_1 | {<p>pprev | <n>next}", group = list];
node_2[label = "<m>hlist_node_2 | {<p>pprev | <n>next}", group = list];
NULL1[shape = plaintext, label = "NULL", group = list]
{rank = "min" hlist_head}
hlist_head -> node_1 -> node_2 [
weight = 10, style = invis
]
hlist_head -> node_n [style = invis]
node_n -> hlist_head [style = invis]
hlist_head:n -> node_n:m[color=red];
node_1:n -> node_2:m;
node_2:p -> node_1:n;
node_2:n -> NULL1;
node_1:p -> node_n:n;
{rank=same;node_1;}
node_n:n -> node_1:m;
node_n:p -> hlist_head:n;
}
```
- 鏈結串列為空
```graphviz
digraph G {
rankdir = LR;
splines = false;
node[shape = "record"]
hlist_head[label = "<m>hlist_head | <n>first"]
NULL_1[shape = plaintext, label = "NULL", group = list]
node_n[label = "<m>hlist_node_n | {<p>pprev | <n>next}", group = list];
hlist_head:n -> NULL_1
NULL_1 -> node_n:p [style = invis]
}
```
```c
#5 n->next = h->first; //AAAA
```
```graphviz
digraph G {
rankdir = LR;
splines = false;
node[shape = "record"]
hlist_head[label = "<m>hlist_head | <n>first"]
node_n[label = "<m>hlist_node_n | {<p>pprev | <n>next}", group = list];
NULL_1[shape = plaintext, label = "NULL", group = list]
hlist_head:n -> NULL_1
node_n:n ->NULL_1 [color = red]
}
```
```c
#6 n->pprev = &h->first;
```
```graphviz
digraph G {
rankdir = LR;
//splines = false;
node[shape = "record"]
hlist_head[label = "<m>hlist_head | <n>first"]
node_n[label = "<m>hlist_node_n | {<p>pprev | <n>next}", group = list];
NULL_1[shape = plaintext, label = "NULL", group = list]
// list_head:n -> NULL_1
node_n:n ->NULL_1
node_n:p ->hlist_head:n [color=red]
hlist_head:n -> NULL_1 [ headport="s" splines=curve ]
{rank="min" hlist_head}
}
```
```c
#7 h->first = n;
```
```graphviz
digraph G {
rankdir = LR;
splines = false;
node[shape = "record"]
hlist_head[label = "<m>hlist_head | <n>first"]
node_1[label = "<m>hlist_node_n | {<p>pprev | <n>next}", group = list];
NULL[shape = plaintext, label = "NULL", group = list]
{rank = "min" hlist_head}
hlist_head -> node_1[
weight = 10, style = invis
]
node_1:n -> NULL;
node_1:p -> hlist_head:n;
hlist_head:n -> node_1:m[color=red];
}
```
```c=
static int find(int num, int size, const struct hlist_head *heads)
{
struct hlist_node *p;
int hash = (num < 0 ? -num : num) % size;
hlist_for_each (p, BBBB) {
struct order_node *on = CCCC(p, struct order_node, node);
if (num == on->val)
return on->idx;
}
return -1;
}
```
從函式名稱 `find` 及回傳值型態可以推測出此函示去尋找值為 num 的節點 ,並返回該節點的 idx。
首先觀察第 4 行,這邊的雜湊函數是採用 Division method ,共有 size 個 bucket,`hash` 的值為 num 所屬的第幾個 bucket , 接下來第五行去該 bucket 所指向的鏈結串列去走訪每個節點尋找,可以看出 `BBBB = &heads[hash]` 。因為要把 `num` 與每個節點的 `val` 進行比較是否相等,需要知道 `order_node` 這個結構體起始地址,可以透過 `container_of` 這個巨集來得到 ,
<s>
因此 `CCCC=container_of` 或 `CCCC=list_entry`。
</s>
:::danger
不用列出參考題解,專注在程式碼的探討。
:::
```c
static inline void node_add(int val,
int idx,
int size,
struct hlist_head *heads)
{
struct order_node *on = malloc(sizeof(*on));
on->val = val;
on->idx = idx;
int hash = (val < 0 ? -val : val) % size;
hlist_add_head(&on->node, DDDD);
}
```
函式 `node_add()` 依節點的雜湊值將節點插入所屬 bucket 鏈結串列的開頭,因此 `DDDD = &heads[hash]`
```c=
static struct TreeNode *buildTree(int *preorder,
int preorderSize,
int *inorder,
int inorderSize)
{
struct hlist_head *in_heads = malloc(inorderSize * sizeof(*in_heads));
for (int i = 0; i < inorderSize; i++)
INIT_HLIST_HEAD(&in_heads[i]);
for (int i = 0; i < inorderSize; i++)
node_add(inorder[i], i, inorderSize, in_heads);
return dfs(preorder, 0, preorderSize - 1, inorder, 0, inorderSize - 1,
in_heads, inorderSize);
}
```
函示 `build_tree()` 依中序和前序建一顆二元樹,為了方便說明,假設 Input: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7] 。
首先上段程式碼會先建立一個雜湊表 , bucket 的數量等於 inorderSize ,按照上面的假設,共有 5 個 buckets ,接下來對每個鍊節串列的頭進行初始化。
示意圖如下:
```graphviz
digraph G {
rankdir=LR;
splines = false;
node[shape=record];
map [label="hlist_head.first |<ht0> [0] |<ht1>[1] |<ht2>[2] |<ht3>[3] |<ht4>[4]"];
NULL_0,NULL_1,NULL_2,NULL_3,NULL_4[shape = plaintext,width=0.1 label = "NULL", group = list]
map:ht0 -> NULL_0
map:ht1 -> NULL_1
map:ht2 -> NULL_2
map:ht3 -> NULL_3
map:ht4 -> NULL_4
}
```
接下來依序將 inorder 裡的元素經過雜湊函數,插入其所屬的鏈結串列
示意圖如下:
```graphviz
digraph G {
rankdir=LR;
splines=false;
node[shape=record];
map [label="hlist_head.first |<ht0>[0] |<ht1>[1] |<ht2>[2] |<ht3>[3] |<ht4> [4] "];
NULL_0,NULL_1,NULL_2,NULL_3[shape = plaintext,width=0.1 label = "NULL", group = list]
node[shape="Mrecord"];
node_0[label = "<v>val:9 |<i>idx:0|<h>hlist_node |{<p>pprev | <n>next}"];
node_1[label = "<v>val:3 |<i>idx:1|<h>hlist_node |{<p>pprev | <n>next}"];
node_2[label = "<v>val:15|<i>idx:2|<h>hlist_node | {<p>pprev | <n>next}"];
node_3[label = "<v>val:20|<i>idx:3|<h>hlist_node | {<p>pprev | <n>next}"];
node_4[label = "<v>val:7 |<i>idx:4|<h>hlist_node | {<p>pprev | <n>next}"];
map:ht4 -> node_0:h
node_0:p ->map:ht4
node_0:n ->NULL_0
map:ht3 -> node_1:h
node_1:p ->map:ht3
node_1:n ->NULL_1
map:ht0 -> node_3:h
node_3:p -> map:ht0
node_3:n -> node_2:h
node_2:p -> node_3:n
node_2:n -> NULL_3
map:ht2 ->node_4:h
node_4:p ->map:ht2
node_4:n ->NULL_2
}
```
最後程式遞迴呼叫 `dfs()`
```c
static struct TreeNode *dfs(int *preorder,
int pre_low,
int pre_high,
int *inorder,
int in_low,
int in_high,
struct hlist_head *in_heads,
int size)
{
if (in_low > in_high || pre_low > pre_high)
return NULL;
struct TreeNode *tn = malloc(sizeof(*tn));
tn->val = preorder[pre_low];
int idx = find(preorder[pre_low], size, in_heads);
tn->left = dfs(preorder, pre_low + 1, pre_low + (idx - in_low), inorder,
in_low, idx - 1, in_heads, size);
tn->right = dfs(preorder, pre_high - (in_high - idx - 1), pre_high, inorder,
idx + 1, in_high, in_heads, size);
return tn;
}
```
### 測驗二
### 測驗三
:::danger
不用抄寫題目,專注探討程式原理、闡述你的洞見,還有你預期做哪些改進。
:::
```c
#define __const_hweight8(w) \
((unsigned int) ((!!((w) & (1ULL << 0))) + (!!((w) & (1ULL << 1))) + \
(!!((w) & (1ULL << 2))) + (!!((w) & (1ULL << 3))) + \
(!!((w) & (1ULL << 4))) + (!!((w) & (1ULL << 5))) + \
(!!((w) & (1ULL << 6))) + (!!((w) & (1ULL << 7)))))
```
首先觀察這個巨集,拆分成多個小項進行分析。先來看 `(!!((w) & (1ULL << 0))`, w 與常數 1 進行 bitwise AND 再經過二次 NOT,根據 [C 語言的 bit-field](https://hackmd.io/@sysprog/c-bitfield) 裡所提及的,經過兩次 NOT,能確保結果一定是 0 或 1 ,若 w 的 least significant bit 為 1,則運算結果為 1 。可以歸納出 `(!!((w) & (1ULL << n))` ,若 w 的第 n 位(從低位開始) bit 為 1 ,則運算結果為 1。總結來說,這個巨集就是對 w 的 least significant byte 進行 [population count](https://en.wikichip.org/wiki/population_count#google_vignette) , `__const_hweight16(w)` 就是對最低位的兩個 BYTE 進行 popcount ,其餘以此類推。