# 2020q1 Homework1 (lab0)
contributed by < `nickyanggg` >
###### tags: `linux2020`
## 作業說明
- [lab0](https://hackmd.io/@sysprog/linux2020-lab0)
- [C Programming Lab](http://www.cs.cmu.edu/~213/labs/cprogramminglab.pdf)
## 作業要求
依照作業說明完成 queue.c & queue.h 二個檔案,主要功能如下:
- `q_new` 建立新的 empty queue
- `q_free` 釋放 queue 所用到的空間
- `q_insert_head` 插入新的 element 至 queue 的開頭
- `q_insert_tail` 插入新的 element 至 queue 的尾端
- `q_remove_head` 刪除 queue 的開頭的 element
- `q_size` 計算 queue 中的 element 數量
- `q_reverse` 將 queue 中的 element 依反向順序排列
- `q_sort` 將queue 中的 element 依遞增順序排列
## 實驗環境
```shell
$ uname -a
Linux ginoah-ubuntu-18 5.3.0-40-generic #32~18.04.1-Ubuntu SMP Mon Feb 3 14:05:59 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
$ gcc --version
gcc (Ubuntu 7.4.0-1ubuntu1~18.04.1) 7.4.0
```
## 實作流程
### **queue.h**
由於 `q_insert_tail`、`q_size` 皆要求複雜度 $O(1)$,因此將 `queue_t` 修改如下:
```cpp
typedef struct {
list_ele_t *head; /* Linked list of elements */
list_ele_t *tail;
int size;
} queue_t;
```
如此,當實作 `q_insert_tail`、`q_size` 時,可以不用重頭 traverse 一遍,前者可以直接利用 `tail` 去做串接,而後者則是可以直接回傳 `size` 的值。
### **queue.c**
- **`q_new`**
- 確認 malloc 是否成功。
```c=
queue_t *q_new()
{
queue_t *q = malloc(sizeof(queue_t));
if (!q)
return NULL;
q->head = NULL;
q->tail = NULL;
q->size = 0;
return q;
}
```
- **`q_free`**
- 確認 queue 是否存在。
- 依序釋放各個 element 所用到的空間。
- 最後在釋放 empty queue 所用到的空間。
```c=
void q_free(queue_t *q)
{
/* Free queue structure */
if (!q)
return;
list_ele_t *curr = q->head;
list_ele_t *temp;
for (int i = 0; i < q->size; ++i) {
free(curr->value);
temp = curr->next;
free(curr);
curr = temp;
}
free(q);
}
```
- **`q_insert_head`**
- 確認 queue 是否存在。
- 確認 malloc 是否成功。
- 若在 malloc 空間給 `value` 失敗時,要回頭去 free 前面 malloc 給新進 element 的空間。
- 維護 `head`、`tail`、`size`。
```c=
bool q_insert_head(queue_t *q, char *s)
{
list_ele_t *newh;
if (!q)
return false;
newh = malloc(sizeof(list_ele_t));
if (!newh)
return false;
newh->value = malloc(strlen(s) + 1);
if (!newh->value) {
free(newh);
return false;
}
strncpy(newh->value, s, strlen(s) + 1);
newh->next = q->head;
if (!q->head)
q->tail = newh;
q->head = newh;
q->size++;
return true;
}
```
- **`q_insert_tail`**
- 同 `q_insert_head` 的注意事項。
```c=
bool q_insert_tail(queue_t *q, char *s)
{
if (!q)
return false;
list_ele_t *newt;
newt = malloc(sizeof(list_ele_t));
if (!newt)
return false;
newt->value = malloc(strlen(s) + 1);
if (!newt->value) {
free(newt);
return false;
}
strncpy(newt->value, s, strlen(s) + 1);
newt->next = NULL;
if (!q->head)
q->head = newt;
else
q->tail->next = newt;
q->tail = newt;
q->size++;
return true;
}
```
- **`q_remove_head`**
- 確認 queue 是否存在,以及 `size` 是否為 0。
- 若 `sp` 存在,copy 欲刪的 element 的 value 進去,記得在最後面補上結束字元 `'\0'`,詳情請參考 [strncpy](https://linux.die.net/man/3/strncpy)。
- 釋放所用到的空間,並且維護 `head`、`tail`、`size`。
```c=
bool q_remove_head(queue_t *q, char *sp, size_t bufsize)
{
if (!q || !q->size)
return false;
if (sp) {
strncpy(sp, q->head->value, bufsize - 1);
*(sp + bufsize - 1) = '\0';
}
free(q->head->value);
list_ele_t *head = q->head;
q->head = q->head->next;
free(head);
if (!q->head)
q->tail = NULL;
q->size--;
return true;
}
```
- **`q_size`**
- 確認 queue 是否存在。
```c=
int q_size(queue_t *q)
{
if (!q)
return 0;
return q->size;
}
```
- **`q_reverse`**
- 確認 queue 是否存在以及 `size <= 1` 的狀況。
- 利用 `curr`、`prev`、`temp` 來把 queue 中所有的 elements 逆向接起來。
- 將 `head`、`tail` 的值調換。
```c=
void q_reverse(queue_t *q)
{
if (!q || q->size <= 1)
return;
list_ele_t *curr, *prev, *temp;
curr = q->head;
prev = NULL;
for (int i = 0; i < q->size; ++i) {
temp = curr->next;
curr->next = prev;
prev = curr;
curr = temp;
}
temp = q->head;
q->head = q->tail;
q->tail = temp;
}
```
- **`q_sort`**
- 確認 queue 是否存在以及 `size <= 1` 的狀況。
- 由於複雜度得為 $O(nlogn)$ ,這邊是直接 call 另外實作的 `merge_sort`。
- 維護 `tail` 的值。
```c=
void q_sort(queue_t *q)
{
if (!q || q->size <= 1)
return;
q->head = merge_sort(q->head);
list_ele_t *curr = q->head;
for (int i = 0; i < q->size; ++i) {
q->tail = curr;
curr = curr->next;
}
}
```
- **`merge_sort`**
- 參考 [Linked List Sort](https://npes87184.github.io/2015-09-12-linkedListSort/) 的實作方式。
- `merge_sort`
- 由於假如每次在 divide 的時候 `left` 都 assign 成 `head`,而 `right` 則是 `head->next` 的話,也就是左半段的 queue 只有一個 element,而右半段的 queue 則是剩下的所有 element,這樣子的複雜度仍為 $O(n^2)$,因此下面會介紹將 `left`、`right` 平均分配的方式。
- `left`、`right` 前者每次跳一個 element,後者每次跳二個 element,當 right 到 queue 的尾端時,`left` 所指向的 element 剛好位於 queue 的中間,接著再將 `right` assign 成 `left->next` ( 也就是右邊半段的頭 ),再把 `left->next` assign 成 `NULL` ( 也就是斷開成二個 queue ),最後再把 `left` assign 成 `head` 即可,此時 `left`、`right` 各代表著左半段的 `head` 以及右半段的 `head`。
- 最後再利用遞迴去一直 divide,最後再 call `merge` 來把二段已經 sort 好的 queue 給 merge 起來。
```c=
list_ele_t *merge_sort(list_ele_t *head)
{
if (!head || !head->next)
return head;
list_ele_t *left = head;
list_ele_t *right = head->next;
while (right && right->next) {
left = left->next;
right = right->next->next;
}
right = left->next;
left->next = NULL;
left = merge_sort(head);
right = merge_sort(right);
return merge(left, right);
}
```
- **`merge`**
- 由於測資所塞進的 element 過多,因此在呼叫 `merge` 的時候,不可利用遞迴的方式,不然會發生 `stack overflow` 導致 `segmentation fault`。
```c=
// 非遞迴的版本
list_ele_t *merge(list_ele_t *left, list_ele_t *right)
{
list_ele_t *head, *curr;
if (strcmp(left->value, right->value) <= 0) {
head = left;
left = left->next;
} else {
head = right;
right = right->next;
}
curr = head;
while (left && right) {
if (strcmp(left->value, right->value) <= 0) {
curr->next = left;
left = left->next;
} else {
curr->next = right;
right = right->next;
}
curr = curr->next;
}
if (left)
curr->next = left;
if (right)
curr->next = right;
return head;
}
```
```c=
// 遞迴的版本
list_ele_t *merge(list_ele_t *left, list_ele_t *right)
{
if (!left)
return right;
if (!right)
return left;
if (strcmp(left->value, right->value) < 0) {
left->next = merge(left->next, right);
return left;
}
else {
right->next = merge(left, right->next);
return right;
}
}
```
## 測試結果
利用 `make test` 觀看結果
```shell
make test
scripts/driver.py -c
--- Trace Points
+++ TESTING trace trace-01-ops:
# Test of insert_head and remove_head
--- trace-01-ops 6/6
+++ TESTING trace trace-02-ops:
# Test of insert_head, insert_tail, and remove_head
--- trace-02-ops 6/6
+++ TESTING trace trace-03-ops:
# Test of insert_head, insert_tail, reverse, and remove_head
--- trace-03-ops 6/6
+++ TESTING trace trace-04-ops:
# Test of insert_head, insert_tail, size, and sort
--- trace-04-ops 6/6
+++ TESTING trace trace-05-ops:
# Test of insert_head, insert_tail, remove_head, reverse, size, and sort
--- trace-05-ops 5/5
+++ TESTING trace trace-06-string:
# Test of truncated strings
--- trace-06-string 6/6
+++ TESTING trace trace-07-robust:
# Test operations on NULL queue
--- trace-07-robust 6/6
+++ TESTING trace trace-08-robust:
# Test operations on empty queue
--- trace-08-robust 6/6
+++ TESTING trace trace-09-robust:
# Test remove_head with NULL argument
--- trace-09-robust 6/6
+++ TESTING trace trace-10-malloc:
# Test of malloc failure on new
--- trace-10-malloc 6/6
+++ TESTING trace trace-11-malloc:
# Test of malloc failure on insert_head
--- trace-11-malloc 6/6
+++ TESTING trace trace-12-malloc:
# Test of malloc failure on insert_tail
--- trace-12-malloc 6/6
+++ TESTING trace trace-13-perf:
# Test performance of insert_tail
--- trace-13-perf 6/6
+++ TESTING trace trace-14-perf:
# Test performance of size
--- trace-14-perf 6/6
+++ TESTING trace trace-15-perf:
# Test performance of insert_tail, size, reverse, and sort
--- trace-15-perf 6/6
+++ TESTING trace trace-16-perf:
# Test performance of sort with random and descending orders
# 10000: all correct sorting algorithms are expected pass
# 50000: sorting algorithms with O(n^2) time complexity are expected failed
# 100000: sorting algorithms with O(nlogn) time complexity are expected pass
--- trace-16-perf 6/6
+++ TESTING trace trace-17-complexity:
# Test if q_insert_tail and q_size is constant time complexity
Probably constant time
Probably constant time
--- trace-17-complexity 5/5
--- TOTAL 100/100
```