---
# System prepended metadata

title: 2020q3 Homework1 (lab0)

---

# 2020q3 Homework1 (lab0)
contributed by < `nelsonlai1` >

## 開發環境
```
$ uname -a
Linux itlab-right 5.4.0-42-generic #46~18.04.1-Ubuntu SMP Fri Jul 10 07:21:24 UTC 2020 
x86_64 x86_64 x86_64 GNU/Linux
$ gcc --version
gcc (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0
Copyright (C) 2017 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
```
## function 功能
1. **q_new** : 新增queue
2. **q_free** : 釋放queue中所有記憶體
3. **q_insert_head** : 在queue最前面加入node
4. **q_insert_tail** : 在queue最後面加入node
5. **q_remove_head** : 刪除queue最前方的node，將刪除的元素存入sp中
6. **q_size** : 回傳queue大小
7. **q_reverse** : 將queue順序顛倒
8. **q_sort** : 將queue由小排到大

## 開發過程
首先依照作業要求中的提示，在**queue.h**中的`struct queue_t`加入
``` c=
list_ele_t *tail;
int size;
```
接著開始修改**queue.c**中的function : 
### **q_new** : 
``` c=
queue_t *q_new()
{
    queue_t *q = malloc(sizeof(queue_t));
    if (q == NULL)
        return NULL;
    q->head = q->tail = NULL;
    q->size = 0;
    return q;
}
```
這個function挺基本的，先分配一個queue_t大小的空間給q，若分配失敗回傳NULL，反之則對q做初始化，將head與tail都指向NULL，size設為0。
 ### **q_free** : 
 ``` c=
 void q_free(queue_t *q)
{
    if (q == NULL)
        return;
    while (q->head != NULL) {
        list_ele_t *tmp = q->head;
        q->head = q->head->next;
        free(tmp->value);
        free(tmp);
    }
    free(q);
}
```
首先先確認q不為NULL後，在q不為empty的條件下，先將`q->head`以一個`tmp`儲存，將   
`q->head`移動到下一位後，再將原本的`q->head`(tmp)釋放，最後再將q釋放。

如此一來，便能夠從頭開始輪一次後將所有node釋放。
### **q_insert_head** : 
```c=
bool q_insert_head(queue_t *q, char *s)
{
    if (q == NULL)
        return false;
    list_ele_t *newh;
    newh = malloc(sizeof(list_ele_t));
    if (newh == NULL)
        return false;
    newh->value = malloc(sizeof(char) * (strlen(s) + 1));
    if (newh->value == NULL) {
        free(newh);
        return false;
    }
    snprintf(newh->value, strlen(s) + 1, "%s", s);
    if (q->head != NULL)
        newh->next = q->head;
    else {
        q->tail = newh;
        newh->next = NULL;
    }
    q->head = newh;
    q->size++;
    return true;
}
```
前半段(13行前)在對分配記憶體位置以及錯誤做處理，`newh->value`的部分由於"\0"字元的關係要多分配一個char的空間給它。

snprintf這個function在gcc編譯後會先將要被寫入的string(newh->value)清空，再將要寫入的string(s)補"/0"後寫入，非常適合在這次作業中使用。

接著判斷`q->head`是否存在，存在的話將`q->head` assign給`newh->next`，不存在(q為empty)時，`q->tail`也指向`newh`，同時因為`newh`是唯一一個node，故把`newh->next`指向NULL。最後無論如何都將`q->head`指向`newh`，並把`q->size`加一。
### **q_insert_tail** : 
``` 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(sizeof(char) * (strlen(s) + 1));
    if (newt->value == NULL) {
        free(newt);
        return false;
    }
    snprintf(newt->value, strlen(s) + 1, "%s", s);
    newt->next = NULL;
    if (q->tail != NULL)
        q->tail->next = newt;
    else
        q->head = newt;
    q->tail = newt;
    q->size++;
    return true;
}
```
前面與`q_insert_head`雷同，不再贅述。

後面則先把`newt->next`指向NULL，若存在`q->tail`則將`q->tail->next`指向newt，反之(q為empty)則需要更新`q->head`，與`q_insert_head`類似。
### **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)
        snprintf(sp, bufsize, "%s", q->head->value);
    list_ele_t *rm = q->head;
    q->head = q->head->next;
    free(rm->value);
    free(rm);
    q->size--;
    if (q->size == 0)
        q->tail = NULL;
    return true;
}
```
首先判斷q是否為NULL或empty，是的話返回false。

再來看sp是否存在，存在時用snprintf來寫入被刪除的值，不存在則不管它。

接著的概念與`q_free`類似，不過只做一次，最後判斷如果q變成empty的話，將`q->tail`指向NULL。
### **q_size** : 
``` c=
int q_size(queue_t *q)
{
    if (q == NULL)
        return 0;
    return q->size;
}
```
判斷q是否為empty，如果是的話返回0，不是的話返回`q->size`。

這function雖然簡單，但不知為何有時候make test時會判斷為不是O(1)。
### **q_reverse** : 
``` c=
{
    if (q == NULL || q->head == q->tail)
        return;
    list_ele_t *cursor = NULL;
    q->tail = q->head;
    while (q->head != NULL) {
        list_ele_t *next = q->head->next;
        q->head->next = cursor;
        cursor = q->head;
        q->head = next;
    }
    q->head = cursor;
}
```
這個function是由quiz1改寫而來的。

首先判斷q是否不存在或是element為0或1，若是的話因為無須reverse直接`return`。令一個`cursor`指向NULL，這個`cursor`會一直跟著`q->head`移動，在`q->head`以及reverse前的`q->head`前一項轉換。

while迴圈中，首先令一個`next`儲存原本的`q->head->next`(原本第二項element)，將`q->head->next`指向`cursor`(此時`cursor`為NULL，也就是將原本的第一項改為最後一項)，將`cursor`指到`q->head`(現在為最後一項)，將`q->head`指向`next`(即將成為倒數第二項)。如此一來，最後`cursor`會指向reverse完的第一項，`q->head`則是指向NULL。

最後把`q->head`指向`cursor`。
### **q_sort** : 
``` c=
void q_sort(queue_t *q)
{
    if (q == NULL || q->head == q->tail)
        return;
    q->head = q->tail = merge_sort(q->head);
    while (q->tail->next != NULL)
        q->tail = q->tail->next;
}
list_ele_t *merge_sort(list_ele_t *head)
{
    if (head == NULL || head->next == NULL)
        return head;
    list_ele_t *fast = head->next;
    list_ele_t *slow = head;
    while (fast != NULL && fast->next != NULL) {
        slow = slow->next;
        fast = fast->next->next;
    }
    fast = slow->next;
    slow->next = NULL;
    list_ele_t *l1 = merge_sort(head);
    list_ele_t *l2 = merge_sort(fast);
    return merge(l1, l2);
}

list_ele_t *merge(list_ele_t *l1, list_ele_t *l2)
{
    list_ele_t *head = NULL;
    list_ele_t **tmp = &head;
    while (l1 != NULL && l2 != NULL) {
        if (strcmp(l1->value, l2->value) < 0) {
            *tmp = l1;
            tmp = &(*tmp)->next;
            l1 = l1->next;
        } else {
            *tmp = l2;
            tmp = &(*tmp)->next;
            l2 = l2->next;
        }
    }
    if (l1 != NULL)
        *tmp = l1;
    if (l2 != NULL)
        *tmp = l2;
    return head;
}
```
由於作業要求O(nlgn)，首先會決定要用quicksort或mergesort，但考慮到quicksort若不做處理的話會在worst case時變成O(n^2)，且在linked list實作也較麻煩，這裡使用mergesort。

一開始想到既然已經定義`q->size`以及諸多function，於是直接建立額外的queue來進行mergesort，但沒想到在make test時直接失敗了，應該是使用太多的記憶體空間，但又不忍心刪掉，只好放在註解中。

最後只好乖乖~~抄襲~~參考老師提供的[Linked List Sort](https://npes87184.github.io/2015-09-12-linkedListSort/)做改寫。為了recursive需求，另外令兩個function(同時也要在**queue.h**新增)

在`merge_sort`中，先定義終止條件，當元素為0或1時回傳`head`。接著的技巧利用`fast`速度為`slow`的兩倍，當`fast`跑到末端時，`slow`會位於中間，再將`fast`指向`slow->next`變成後半段，而`slow->next`指向NULL來將前半段分割出來。

`merge`中，以一個`head`以及`head`的pointer `tmp`來將`l1`, `l2`整合排序，使用`tmp`的原因是為了不讓`head`位置跑掉。while迴圈內將`l1`為頭的list, `l2`為頭的list做比較，並依序將`*tmp`指向小的元素，最後當一方沒了時，將`*tmp`指向剩餘的一方，最後return `head`時就會是排序好的。

## Valgrind & Massif

首先輸入
```
make valgrind
```
執行Valgrind

接著用
```
valgrind --tool=massif ./qtest -v 3 -f traces/trace-eg.cmd
```
執行Massif

```
cmd> 
cmd> # Demonstration of queue testing framework
cmd> # Use help command to see list of commands and options
cmd> # Initial queue is NULL.
cmd> show
q = NULL
cmd> # Create empty queue
cmd> new
q = []
cmd> # Fill it with some values.  First at the head
cmd> ih dolphin
q = [dolphin]
cmd> ih bear
q = [bear dolphin]
cmd> ih gerbil
q = [gerbil bear dolphin]
cmd> # Now at the tail
cmd> it meerkat
q = [gerbil bear dolphin meerkat]
cmd> it bear
q = [gerbil bear dolphin meerkat bear]
cmd> # Reverse it
cmd> reverse
q = [bear meerkat dolphin bear gerbil]
cmd> # See how long it is
cmd> size
Queue size = 5
q = [bear meerkat dolphin bear gerbil]
cmd> # Delete queue.  Goes back to a NULL queue.
cmd> free
q = NULL
cmd> # Exit program
cmd> quit
Freeing queue
```
再輸入
```
ms_print massif.out.[數字](輸出的檔案名)
```
來看massif分析結果
```
--------------------------------------------------------------------------------
Command:            ./qtest -v 3 -f traces/trace-eg.cmd
Massif arguments:   (none)
ms_print arguments: massif.out.14570
--------------------------------------------------------------------------------


    KB
11.16^                                                                    #   
     |                                                         :  :::@:@::#:  
     |                                                ::@::@:::::::: @ @::#:: 
     |                                                ::@::@:::::::: @ @::#:: 
     |                                                ::@::@:::::::: @ @::#:: 
     |                                                ::@::@:::::::: @ @::#:: 
     |                                                ::@::@:::::::: @ @::#:: 
     |                                                ::@::@:::::::: @ @::#:: 
     |                                                ::@::@:::::::: @ @::#:: 
     |                                                ::@::@:::::::: @ @::#:: 
     |                                                ::@::@:::::::: @ @::#:: 
     |                                                ::@::@:::::::: @ @::#:: 
     |                                                ::@::@:::::::: @ @::#:: 
     |                                                ::@::@:::::::: @ @::#:: 
     |                                                ::@::@:::::::: @ @::#:: 
     |                                                ::@::@:::::::: @ @::#:: 
     |                                                ::@::@:::::::: @ @::#:: 
     |                                                ::@::@:::::::: @ @::#:: 
     |                                                ::@::@:::::::: @ @::#:: 
     |                                               @::@::@:::::::: @ @::#::@
   0 +----------------------------------------------------------------------->ki
     0                                                                   314.8

Number of snapshots: 56
 Detailed snapshots: [4, 10, 18, 37, 41, 47 (peak), 54]
```
詳細的記憶體使用狀況，這裡只列出一部分
```
--------------------------------------------------------------------------------
  n        time(i)         total(B)   useful-heap(B) extra-heap(B)    stacks(B)
--------------------------------------------------------------------------------
  0              0                0                0             0            0
  1        209,271               40               32             8            0
  2        211,027              544              416           128            0
  3        212,471              704              544           160            0
  4        214,104              824              640           184            0
77.67% (640B) (heap allocation functions) malloc/new/new[], --alloc-fns, etc.
->77.67% (640B) 0x10B4CF: malloc_or_fail (report.c:189)
  ->58.25% (480B) 0x10BFE1: add_cmd (console.c:123)
  | ->03.88% (32B) 0x10AD05: main (qtest.c:82)
  | | 
  | ->03.88% (32B) 0x10AD1F: main (qtest.c:83)
  | | 
  | ->03.88% (32B) 0x10AD39: main (qtest.c:84)
  | | 
  | ->03.88% (32B) 0x10AD53: main (qtest.c:87)
  | | 
  | ->03.88% (32B) 0x10AD6D: main (qtest.c:90)
  | | 
  | ->03.88% (32B) 0x10AD87: main (qtest.c:93)
  | | 
  | ->03.88% (32B) 0x10ADA1: main (qtest.c:96)
  | | 
  | ->03.88% (32B) 0x10ADBB: main (qtest.c:97)
  | | 
  | ->03.88% (32B) 0x10C0C2: init_cmd (console.c:93)
  | | ->03.88% (32B) 0x10ACEB: main (qtest.c:759)
  | |   
  | ->03.88% (32B) 0x10C0DC: init_cmd (console.c:94)
  | | ->03.88% (32B) 0x10ACEB: main (qtest.c:759)
  | |   
  | ->03.88% (32B) 0x10C0F6: init_cmd (console.c:96)
  | | ->03.88% (32B) 0x10ACEB: main (qtest.c:759)
  | |   
  | ->03.88% (32B) 0x10C110: init_cmd (console.c:97)
  | | ->03.88% (32B) 0x10ACEB: main (qtest.c:759)
  | |   
  | ->03.88% (32B) 0x10C12A: init_cmd (console.c:99)
  | | ->03.88% (32B) 0x10ACEB: main (qtest.c:759)
  | |   
  | ->03.88% (32B) 0x10C144: init_cmd (console.c:100)
  | | ->03.88% (32B) 0x10ACEB: main (qtest.c:759)
  | |   
  | ->03.88% (32B) 0x10C15E: init_cmd (console.c:101)
  |   ->03.88% (32B) 0x10ACEB: main (qtest.c:759)
  |     
  ->19.42% (160B) 0x10C057: add_param (console.c:144)
    ->04.85% (40B) 0x10C17D: init_cmd (console.c:102)
    | ->04.85% (40B) 0x10ACEB: main (qtest.c:759)
    |   
    ->04.85% (40B) 0x10C19C: init_cmd (console.c:104)
    | ->04.85% (40B) 0x10ACEB: main (qtest.c:759)
    |   
    ->04.85% (40B) 0x10C1BB: init_cmd (console.c:105)
    | ->04.85% (40B) 0x10ACEB: main (qtest.c:759)
    |   
    ->04.85% (40B) 0x10C1DA: init_cmd (console.c:106)
      ->04.85% (40B) 0x10ACEB: main (qtest.c:759)
```