# 2020q3 Homework1 (lab0)
contributed by <`ngokchaoho`>
## 作業要求
[queue.c](https://github.com/sysprog21/lab0-c/blob/master/queue.c) 僅提供介面 ([interface](https://en.wikipedia.org/wiki/Interface-based_programming)) 但尚未有完整程式實作,需要由學員補完並逐步精進,包含以下:
* `q_new`: 建立新的「空」佇列;
* `q_free`: 釋放佇列所佔用的記憶體;
* `q_insert_head`: 在佇列開頭 (head) 加入 (insert) 給定的新元素 (以 LIFO 準則);
* `q_insert_tail`: 在佇列尾端 (tail) 加入 (insert) 給定的新元素 (以 FIFO 準則);
* `q_remove_head`: 在佇列開頭 (head) 移去 (remove) 給定的元素。
* `q_size`: 計算佇列中的元素數量。
* `q_reverse`: 以反向順序重新排列鏈結串列,該函式不該配置或釋放任何鏈結串列元素,換言之,它只能重排已經存在的元素;
* ==`q_sort`==: 「[Linux 核心設計](http://wiki.csie.ncku.edu.tw/linux/schedule)」課程所新增,以==遞增順序==來排序鏈結串列的元素,可參閱 [Linked List Sort](https://npes87184.github.io/2015-09-12-linkedListSort/) 得知實作手法;
## 系統環境
Distributor ID: Ubuntu
Description: Ubuntu 20.04.1 LTS
Release: 20.04
Codename: focal
我想藉寫這個作業學習使用 Vim,結果寫共筆的時候我發現沒法使用 Clipboard, 在vim底下使用`:version` 會顯示`-clipboard`, Google 以後發現是這個版本的 Vim 不支援 Clipboard的意思。
安裝vtm-gtk以後,就可以使用v鍵選取程式碼,再用`"+y`複製到系統Clipboard, 然後就可以在HackMD上面Ctrl+V了。
```
sudo apt-get install vtm-gtk
```
## 實作流程
#### `q_size`
題目要求q_size是$O(1)$, 所以在queue.t這個struct裡面增加size這個member
```c=
int q_size(queue_t *q)
{
if (q == NULL || q->head == NULL)
return 0;
return q->size;
}
```
#### `q_new`
用 Pointer 檢查了 malloc 是否成功,這裡可以看到queue_t除了 head 和 size, 還有一個 tail 的 member,這是為了可以在$O(1)$ insert tail 而加的。
```c=
queue_t *q_new()
{
queue_t *q = malloc(sizeof(queue_t));
if (q != NULL) {
q->head = NULL;
q->size = 0;
q->tail = NULL;
}
return q;
}
```
#### `q_free`
從 Head 數過去,一個一個釋放。
```c=
void q_free(queue_t *q)
{
if (q == NULL)
return;
if (q->head != NULL) {
list_ele_t *curr_ele = q->head;
while (curr_ele != NULL) {
free(curr_ele->value);
list_ele_t *prev_ele = curr_ele;
if (curr_ele->next != NULL) {
curr_ele = curr_ele->next;
} else {
free(curr_ele);
break;
}
free(prev_ele);
}
}
free(q);
}
```
#### `q_insert_head`
因為要用到兩個malloc,均有可能失敗,所以要free兩次。當只有一個元素時,Head和Tail是指向同一記憶體的。
```c=
bool q_insert_head(queue_t *q, char *s)
{
list_ele_t *newh;
if (q == NULL)
return false;
newh = malloc(sizeof(list_ele_t));
if (newh == NULL)
return false;
newh->value = malloc(strlen(s) + 1);
if (newh->value == NULL) {
free(newh);
return false;
}
strncpy(newh->value, s, strlen(s)+1);
newh->next = q->head;
q->head = newh;
q->size += 1;
if (q->size == 1)
q->tail = q->head;
return true;
}
```
#### `q_remove_head`
```c=
bool q_remove_head(queue_t *q, char *sp, size_t bufsize)
{
if (q == NULL || q->head == NULL)
return false;
if (sp != NULL) {
strncat(sp, q->head->value, bufsize - 1);
}
list_ele_t *prev_head = q->head;
q->head = q->head->next;
free(prev_head->value);
free(prev_head);
q->size -= 1;
return true;
}
```
#### `q_insert_tail`
為了可以在$O(1)$時間內完成,如上所述地在 queue_t 這個 struct 裡面加入了tail, 因此如果不是 empty queue 的話只要記得加 size 和 tail 要指向新的記憶體就可以了, 與 insert head 相同,malloc 有可能失敗,所以如果失敗要free兩次。當只有一個元素時,Head和Tail是指向同一記憶體的。這裡一開始使用了不安全的 strcpy, 因為 destination 的空間是由 malloc source 出來的,如果malloc成功,則不會有 overflow 的問題。
然而 git commit hook不允許strcpy,因此改用strncpy。
```c=
bool q_insert_tail(queue_t *q, char *s)
{
if (q == NULL)
return false;
list_ele_t *newt;
newt = malloc(sizeof(list_ele_t));
if (newt == NULL)
return false;
newt->value = malloc(strlen(s) + 1);
if (newt->value == NULL) {
free(newt);
return false;
}
newt->next = NULL;
strncpy(newt->value, s, strlen(s)+1);
q->size += 1;
if (q->size > 1) {
q->tail->next = newt;
q->tail = newt;
} else {
/*from empty queue*/
q->head = newt;
q->tail = newt;
}
return true;
}
```
#### `q_reverse`
依照註解提示所寫
```c=
void q_reverse(queue_t *q)
{
if (q == NULL || q->head == NULL)
return;
list_ele_t *curr_ele = q->head;
list_ele_t *prev_ele = NULL;
while (curr_ele != q->tail) {
list_ele_t *next_ele = curr_ele->next;
curr_ele->next = prev_ele;
prev_ele = curr_ele;
curr_ele = next_ele;
}
curr_ele->next = prev_ele;
list_ele_t *tmp = q->head;
q->head = q->tail;
q->tail = tmp;
}
```
#### `q_remove_head`
```c=
bool q_remove_head(queue_t *q, char *sp, size_t bufsize)
{
if (q == NULL || q->head == NULL)
return false;
if (sp != NULL) {
strncat(sp, q->head->value, bufsize - 1);
}
list_ele_t *prev_head = q->head;
q->head = q->head->next;
free(prev_head->value);
free(prev_head);
q->size -= 1;
return true;
}
```
#### `q_sort`
參閱 [Linked List Sort](https://npes87184.github.io/2015-09-12-linkedListSort/), 試作了 linked list 的 selection sort,
因為時間複雜度是$O(N^2)$, 所以沒有通過TESTING trace trace-16-perf。預期可以通過改用merge sort解決。
因為不經修改地嵌入了strnatcmp,從網上討論區得知這是不能通過+++ TESTING trace trace-15-perf的其中一個原因,預期可透過解決strnatcmp。
```c=
void q_sort(queue_t *q)
{
/* TODO: You need to write the code for this function */
/* TODO: Remove the above comment when you are about to implement. */
if (q == NULL || q->head == NULL || q->size == 1)
return;
list_ele_t *sorted_tail = q->tail;
list_ele_t *unsorted_head = q->head;
list_ele_t *prev = NULL;
list_ele_t *curr = NULL;
list_ele_t *minNode = NULL;
list_ele_t *min_Prev = NULL;
for (int i = q->size; i > 0; i--) {
curr = unsorted_head;
minNode = unsorted_head;
prev = unsorted_head;
min_Prev = unsorted_head;
for (int j = 0; j < i; j++) {
// printf("%s ",curr->value);
// printf("%s ",minNode->value);
// printf("%d\n",strnatcmp(curr->value,minNode->value));
if (strnatcmp(curr->value, minNode->value) < 0) {
minNode = curr;
min_Prev = prev;
}
curr = curr->next;
if (j != 0)
prev = prev->next;
}
// printf("%s",minNode->value);
sorted_tail->next = minNode;
sorted_tail = sorted_tail->next;
if (minNode == unsorted_head) {
unsorted_head = unsorted_head->next; // moved comparision windows
} else {
min_Prev->next = minNode->next;
}
sorted_tail->next = NULL;
if (i == q->size)
q->head = sorted_tail;
}
q->tail = sorted_tail;
}
```
至此能通過除了15,16,17(17有的時候能通過)以外的unit test。
改用遞迴版的 merge sort 之後,16和17都能通過了。
```c=
list_ele_t *merge(list_ele_t *l1, list_ele_t *l2)
{
if (!l2)
return l1;
if (!l1)
return l2;
if (strnatcmp(l1->value, l2->value) < 0) {
l1->next = merge(l1->next, l2);
return l1;
} else {
l2->next = merge(l1, l2->next);
return l2;
}
}
list_ele_t *merge_sort_list(list_ele_t *head)
{
if (!head || !head->next)
return head;
list_ele_t *fast = head->next;
list_ele_t *slow = head;
while (fast && fast->next) {
slow = slow->next;
fast = fast->next->next;
}
fast = slow->next;
slow->next = NULL;
list_ele_t *l1 = merge_sort_list(head);
list_ele_t *l2 = merge_sort_list(fast);
return merge(l1, l2);
}
/*
* Sort elements of queue in ascending order
* No effect if q is NULL or empty. In addition, if q has only one
* element, do nothing.
*/
void q_sort(queue_t *q)
{
if (q == NULL || q->head == NULL || q->size == 1)
return;
q->head = merge_sort_list(q->head);
list_ele_t *tmp = q->head;
while (tmp->next != NULL)
tmp = tmp->next;
q->tail = tmp;
}
```
經查閲執行 traces 15,發現有segmentation error, 推測是因為用了遞迴,於是修改使用while loop
```c=
list_ele_t *merge(list_ele_t *l1, list_ele_t *l2)
{
list_ele_t *temp = NULL;
list_ele_t *head = NULL;
while (l1 && l2) {
if (strnatcmp(l1->value, l2->value) < 0) {
if (temp == NULL) {
temp = l1;
head = l1;
l1 = l1->next;
} else {
temp->next = l1;
temp = l1;
l1 = l1->next;
}
} else {
if (temp == NULL) {
temp = l2;
head = l2;
l2 = l2->next;
} else {
temp->next = l2;
temp = l2;
l2 = l2->next;
}
}
}
if (l1)
temp->next = l1;
if (l2)
temp->next = l2;
return head;
}
```
至此所有 unit test都能通過了。
```
nch@nch-desktop:~/lab0-c$ 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
```