# 2020q3 Homework6 (quiz6)
contributed by < `jonec76` >
###### tags: `sysprog-2020`
> [作業說明](https://hackmd.io/@sysprog/r1z0WPWLD)
> [題目](https://hackmd.io/@sysprog/2020-quiz6)
## 開發環境
```shell
$ uname -a
Linux jonec76-Aspire-M7720 5.4.0-47-generic #51-Ubuntu SMP GNU/Linux
```
## 參考解答
D1=2
D2=1
ZZZ=1-i
## Q1
## Q2
- 解題想法
此題是要調整 ring buffer 頭跟尾的 index
```c=1
#define NEXT_START_INDEX(BUF) \
(((BUF)->start != (BUF)->size) ? ((BUF)->start + RB1) : 0)
#define NEXT_END_INDEX(BUF) (((BUF)->end != (BUF)->size) ? ((BUF)->end + RB2) : 0)
```
檢查目前的 index 是否還沒有達到 buffer size,如果還沒有達到這 size 就 +1,否則的話則依據 ring buffer 的特性,調整 START/END 的位置到 0。
至於為什麼 macro 裡頭會有下方的程式碼呢?
```c=1
do { ... } while (0)
```
查了 [stack overflow](https://stackoverflow.com/questions/257418/do-while-0-what-is-it-good-for) 中看到,在 `#define` 一個函式時,無法使用 `{}` 將該函式封起來,必須要透過 `while(0)` 的方法才行,若不封裝的話則有可能產生下方的問題,原本的程式碼如下
```c=1
#define FOO(x) { foo(x); bar(x); }
if (condition)
FOO(x)
else
...
```
若把 macro 展開之後將會得到以下的程式碼
```c=1
#define FOO(x) { foo(x); bar(x); }
if (condition)
foo(x);
bar(x);
else
...
```
此時就會發現有部分程式執行結果會有問題,就是因為沒有將函式封裝好。
## Q3
- 解題想法
A~D 的填空,是要在 list 新增一個值,題目是下方程式碼
```c=1
#define cons(x, y) (struct llist[]){{y, x}}
struct llist *list = cons(cons(cons(cons(NULL, A), B), C), D);
```
覺得這寫法很有趣,一步一步看 macro 如何應用在新增 list
```c=1
cons(cons(NULL, A), B)
```
在 `cons(NULL, A)` 對一個 `struct list []` 做初始化並且回傳一個 `struct list []` 的型態,是一個 pointer,這個 pointer 會傳到外層的 `cons` 變成該 `cons` 的 `next`。
所以說,最裡面的 `cons` 所回傳的 list 會在最後一個的位置(tail)。題目希望印出 `9547` 這樣的順序,所以 A 是最右邊尾巴的值 7,D 是最左邊頭的值 9,中間順序以此類推。
接著,看到 `sorted_insert` 題目,此函式是希望能夠邊插新的值,邊做排序,程式碼如下方。
```c=1
void sorted_insert(struct llist **head, struct llist *node)
{
if (!*head || (*head)->val >= node->val) {
SS1;
SS2;
return;
}
struct llist *current = *head;
while (current->next && current->next->val < node->val)
current = current->next;
node->next = current->next;
current->next = node;
}
```
此函式能夠分成兩個部分來看
1. `node->val` 比 `(*head)->val` 小
2. `node->val` 比 `(*head)->val` 大
在函式的下方 while loop 部分是處理 (2) 的部分。每次當有新的值進來時,都要從頭往尾巴跑過一次,透過比大小能夠找出此新的 value 該插入哪個位置。
至於 (1) 的部分,就是在上面 `if` 做處理,可以用下面的圖片表示
會進入 if 條件裡頭的 case,可以用下面的圖來表示
```graphviz
digraph linked_list{
rankdir=LR
node [shape=record];
h [label="{ <data> head | <ref> }"];
2 [ style=filled fillcolor=gray label="{ <data> 2| <ref> }"];
4 [ style=filled fillcolor=gray label="{ <data> 4 | <ref> }"];
6 [ style=filled fillcolor=gray label="{ <data> 6 | <ref> }"];
8 [ style=filled fillcolor=gray label="{ <data> 8 | <ref> }"];
h:ref:e->4:data:n
4:ref:g -> 6:data [arrowtail=dot, dir=both, tailclip=false];
6:ref:h -> 8:data [arrowtail=dot, dir=both, tailclip=false];
}
```
因為 `node->val` 比 `(*head)->val` 的值還小,所以要更改 `head` 的位址。步驟是
1. 將新的 `node` 的 `next` 指向目前 `head` 的位址
2. 把 `head` 改成 `node` 的位址
這個部分概念就跟 [lab0](https://hackmd.io/@jonec76/lab0-c) 的 `q_insert_head` 一樣,換成程式碼如下
```c=1
node->next = *head
*head = node
```
## Q4
- 解題想法
此題的做法蠻有趣的,是使用 bitwise 的方式來解。題目希望能夠找出,在一個給定範圍內,找出 list 裡頭重複的那個 element。看完程式碼後,覺得這題想法可以從離散數學提到的鴿籠原理去思考
1. nums.length == n + 1
2. 1 <= nums[i] <= n
上方的限制將會導致,`nums` 這個陣列中必重複一個數字。如何找出這個數呢?概念簡單來說,是將該 array 以及 range 內的值全部加起來,並讓 array 的總和減去 range 的總和就可以找出哪個值重複了。
看到下方程式碼
```c=1
const size_t log_2 = 8 * sizeof(int) - __builtin_clz(numsSize);
```
首先先找出 `log(numsSize)`,要找出 `log_2` 是因為要知道做 bitwise 的上限在哪邊。
```c=1
int bit = 1 << i; // i = 0~log_2
for (size_t k = 0; k < numsSize; k++) {
if (k & bit) // (A)
++c1;
if (nums[k] & bit) // (B)
++c2;
}
```
這個 for 迴圈的想法就是說,在 A 部分時,會計算出在 range 底下,index=k 的 bit 有多少個 1,同時在 B 部分,是計算出在所有 element,index=k 的 bit 有多少個 1。
根據上述推出來的鴿籠原理,若是 c1=c2 的話,表示這個重複的值轉成二進制後,在第 k 這個 bit 的值為 0。否則的話,就表示該 bit 的值為 1。
```c=1
if (c1 < c2)
res += bit;
```
若為 1 的話,就將該 bit 加到 res。等到跑完了 log_2 的範圍之後,就可以知道重複的值在哪幾個 bit 為 1,即可得到該數值。