# 2019q1 Homework1 (lab0) contributed by < `stanleyuan` > ## 目標 - 使用 linked list 來實作 queue 的 operation - [F01: lab0](https://hackmd.io/s/BJA8EgFB4) - [2019q1 Homework1 (作業區)](https://hackmd.io/s/SJ4kPZYS4) ## 環境 - OS ```shell $ lsb_release -a LSB Version: core-9.20170808ubuntu1-noarch:security-9.20170808ubuntu1-noarch Distributor ID: Ubuntu Description: Ubuntu 18.04.2 LTS Release: 18.04 Codename: bionic ``` - Compiler ```shell $ gcc -v gcc version 8.3.0 (Ubuntu 8.3.0-6ubuntu1~18.04) ``` ## coding style 作業說明有特別提到程式碼一致性的重要,要求使用 [clang format](https://clang.llvm.org/docs/ClangFormat.html) 來檢查程式法風格,老師有說一句話滿重要的「工程師是會溝通的」。最近也是滿有感的,常常是今天的我看不懂昨天的我的程式碼,明天的我可能也是看不懂今天的我的程式碼。推薦 [vim-autoformat](https://github.com/Chiel92/vim-autoformat) 可以一鍵使用系統中的 formatting program。另外還有 [syntastic](https://github.com/vim-syntastic/syntastic) 除了語法檢查之外,也可以使用系統中的 coding style program 來檢查 coding style。 ## Programming Tasks 完成以下的 oprations: q new: Create a new, empty queue. q free: Free all storage used by a queue. q insert head: Attempt to insert a new element at the head of the queue (LIFO discipline). q insert tail: Attempt to insert a new element at the tail of the queue (FIFO discipline). q remove head: Attempt to remove the element at the head of the queue. q size: Compute the number of elements in the queue 另外也要求: q_insert_tail and q_size require that implementations operate in **time O(1)**. ## queue.h - list_ele_t 為 linked list 的 node 的 struct ```cpp /* Linked list element */ typedef struct ELE { /* Pointer to array holding string. */ char *value; struct ELE *next; } list_ele_t; ``` - queue_t 為 queue 的 struct,有一個指向 list_ele_t type 的 pointer。 ```cpp /* Queue structure */ typedef struct { /* Linked list of elements */ list_ele_t *head; } queue_t; ``` ## queue.c - queue 所需實作的 operations * q new * q free * q insert head * q insert tail * q remove head * q size # Implementation ## queue_t 由於題目要求 q_insert_tail and q_size 的 時間複雜度都是 O(1),勢必在 queue_t 中要紀錄 tail node 的位置還有 queue 的大小。需要加上一個 tail 的 pointer 還有整數 size。 新 queue_t: ```cpp /* Queue structure */ typedef struct { list_ele_t *head; /* Linked list of elements */ list_ele_t *tail; int size; } queue_t; ``` ## q_new() 舊 q_new: ```cpp /* Create empty queue. Return NULL if could not allocate space. */ queue_t *q_new() { queue_t *q = malloc(sizeof(queue_t)); /* What if malloc returned NULL? */ q->head = NULL; return q; } ``` 可以看到註解寫到要考慮如果 malloc return NULL,哪些情況下 malloc 會 return NULL呢? ```shell $ man malloc ``` >RETURN VALUE >The malloc() and calloc() functions return a pointer to the allocated memory, which is suitably aligned for any built-in type. **On error, these functions return NULL**. NULL may also be returned by a successful call to malloc() **with a size of zero**, or by a successful call to calloc() with nmemb or size equal to zero. 有錯誤或是 size 為零的時候便會回傳NULL,所以將程式修改為: ```cpp queue_t *q_new() { queue_t *q = malloc(sizeof(queue_t)); /* What if malloc returned NULL? */ if (!q) return q; q->head = NULL; q->tail = NULL; q->size = 0; return q; } ``` 因為已經在 queue_t 裡加上 tail 和 size,也要記得初始化。 ## q_insert_head 舊 q_insert_head: ```cpp bool q_insert_head(queue_t *q, char *s) { list_ele_t *newh; /* What should you do if the q is NULL? */ newh = malloc(sizeof(list_ele_t)); /* Don't forget to allocate space for the string and copy it */ /* What if either call to malloc returns NULL? */ newh->next = q->head; q->head = newh; return true; } ``` 在這裡有 3 種情況要考慮: 1. 如果 q 為 NULL 2. 如果 list_ele_t 的 malloc 回傳 NULL 3. 在 list_ele_t 中 value 所需的空間 malloc 如果回傳 NULL 會變成: ```cpp bool q_insert_head(queue_t *q, char *s) { if (q) { list_ele_t *newh; char *news; int s_len = strlen(s) + 1; newh = malloc(sizeof(list_ele_t)); if (newh) { news = malloc(s_len * sizeof(char)); if (news) { // test later memset(news, '\0', s_len); strcpy(news, s); newh->next = q->head; newh->value = news; if (!q->head) q->tail = newh; q->head = newh; q->size += 1; return true; } free(newh); } } return false; } ``` q->size 要記得加 1 ## q_remove_head 舊 q_remove_head: ```cpp bool q_remove_head(queue_t *q, char *sp, size_t bufsize) { /* You need to fix up this code. */ q->head = q->head->next; return true; } ``` 要判斷有沒有 q, q->head, sp ```cpp bool q_remove_head(queue_t *q, char *sp, size_t bufsize) { if (q && q->head) { list_ele_t *tmp = q->head; memset(sp, '\0', bufsize); if (sp) strncpy(sp, tmp->value, bufsize - 1); q->head = q->head->next; free(tmp->value); free(tmp); q->size -= 1; return true; } return false; } ``` 但一直出錯,後來才發現忘記了q->tail,如果是移除掉最後一個 node 的話: ```cpp bool q_remove_head(queue_t *q, char *sp, size_t bufsize) { if (q && q->head) { list_ele_t *tmp = q->head; memset(sp, '\0', bufsize); if (sp) strncpy(sp, tmp->value, bufsize - 1); q->head = q->head->next; if (!q->head) /* new codes */ q->tail = q->head; /* new codes */ free(tmp->value); free(tmp); q->size -= 1; return true; } return false; } ``` 要記得 free 該 node 的 value 還有 node,q->size 要減一。 ## q_size 舊的 q_size ```cpp int q_size(queue_t *q) { /* You need to write the code for this function */ /* Remember: It should operate in O(1) time */ return 0; } ``` 因為在初始化、新增、刪除都有對 q-?size 做紀錄,如果 q 存在的話,只需回傳 q->size: ```cpp int q_size(queue_t *q) { /* You need to write the code for this function */ /* Remember: It should operate in O(1) time */ if (q) return q->size; return 0; } ``` ## q_insert_tail 舊的 q_insert_tail: ```cpp bool q_insert_tail(queue_t *q, char *s) { /* You need to write the complete code for this function */ /* Remember: It should operate in O(1) time */ return false; } ``` 這邊要注意的是要求 O(1),所以要從 q->tail 下手 ```cpp bool q_insert_tail(queue_t *q, char *s) { /* You need to write the complete code for this function */ /* Remember: It should operate in O(1) time */ if (q) { list_ele_t *tmp = malloc(sizeof(list_ele_t)); char *news; int s_len = strlen(s) + 1; if (tmp) { news = malloc(s_len * sizeof(char)); if (news) { memset(news, '\0', s_len); strcpy(news, s); tmp->value = news; tmp->next = NULL; q->size += 1; if (!q->tail) q->head = tmp; q->tail->next = tmp; q->tail = tmp; return true; } free(tmp); } } return false; } ``` test 過了,但發現我在處理 q->tail 的時候好像怪怪的,如果一開始沒有 nodes,q->tail 就會等於 NULL,那 ```q->tail->next = tmp``` 會指到哪裡? 但 test 過了(? 最後我改成: ```cpp bool q_insert_tail(queue_t *q, char *s) { /* You need to write the complete code for this function */ /* Remember: It should operate in O(1) time */ if (q) { list_ele_t *tmp = malloc(sizeof(list_ele_t)); char *news; int s_len = strlen(s) + 1; if (tmp) { news = malloc(s_len * sizeof(char)); if (news) { memset(news, '\0', s_len); strcpy(news, s); tmp->value = news; tmp->next = NULL; q->size += 1; if (!q->tail) { q->head = tmp; q->tail = tmp; } else { q->tail->next = tmp; q->tail = tmp; } return true; } free(tmp); } } return false; } ``` 才有正確的檢查兩種 cases,測試也過了。 ## q_free 利用 counter 先紀錄 head node,將 head 往前後再把 counter 所指到的 node free 掉。 ```cpp void q_free(queue_t *q) { /* How about freeing the list elements and the strings? */ /* Free queue structure */ if (q) { if (q->head) { while (q->head) { list_ele_t *counter = q->head; q->head = q->head->next; free(counter->value); free(counter); } } free(q); } return; } ``` ## q_reverse 這個比較複雜,要將所有 node 的 next pointer 轉向,例如: ```graphviz digraph revert { //graph [ranksep=4]; labelloc = "b"; graph [splines=ortho]; /* nodes */ node [shape=record, label=""]; q [label="<f0> head | <f1> size | <f2> tail" xlabel="q"] node [shape=plaintext] node1 [label=< <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0"> <TR> <TD PORT="f1">value</TD> <TD PORT="f2">next</TD> </TR> </TABLE>> xlabel="node1"]; node2 [label=< <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0"> <TR> <TD PORT="f1">value</TD> <TD PORT="f2">next</TD> </TR> </TABLE>> xlabel="node2"]; node3 [label=< <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0"> <TR> <TD PORT="f1">value</TD> <TD PORT="f2">next</TD> </TR> </TABLE>> xlabel="node3"]; node4 [label=< <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0"> <TR> <TD PORT="f1">value</TD> <TD PORT="f2">next</TD> </TR> </TABLE>> xlabel="node4"]; node [shape=plaintext, label=""] NULL [label="NULL"] subgraph cluster_node { style=invis; {rank=same node1 node2 node3 node4}; q:f0 -> node1; q:f2 -> node4; edge [headport=w] node1:f2 -> node2; node2:f2 -> node3; node3:f2 -> node4; edge [headport=n] node4:f2 -> NULL; } } ``` 會變成: ```graphviz digraph revert { //graph [ranksep=4]; labelloc = "b"; graph [splines=ortho]; /* nodes */ node [shape=record, label=""]; q [label="<f0> head | <f1> size | <f2> tail" xlabel="q"] node [shape=plaintext] node1 [label=< <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0"> <TR> <TD PORT="f1">value</TD> <TD PORT="f2">next</TD> </TR> </TABLE>> xlabel="node1"]; node2 [label=< <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0"> <TR> <TD PORT="f1">value</TD> <TD PORT="f2">next</TD> </TR> </TABLE>> xlabel="node2"]; node3 [label=< <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0"> <TR> <TD PORT="f1">value</TD> <TD PORT="f2">next</TD> </TR> </TABLE>> xlabel="node3"]; node4 [label=< <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0"> <TR> <TD PORT="f1">value</TD> <TD PORT="f2">next</TD> </TR> </TABLE>> xlabel="node4"]; node [shape=plaintext, label=""] NULL [label="NULL"] subgraph cluster_node1 { style=invis; {rank=same node1 node2 node3 node4}; q:f0 -> node4; q:f2 -> node1; edge [headport=w] node4:f2 -> node3; node3:f2 -> node2; node2:f2 -> node1; edge [headport=n] node1:f2 -> NULL; } } ``` 想法是要用兩個 pointer 來紀錄連續兩個 node 並且變更指標所指向的 node: ```cpp void q_reverse(queue_t *q) { /* You need to write the code for this function */ if (q && q->head && q->head->next) { list_ele_t *left, *right; right = q->head->next; left = q->head; q->tail = q->head; q->tail->next = NULL; while (right) { q->head = right; right = q->head->next; q->head->next = left; left = q->head; } } } ``` ###### tags: `knowThyself` `linux` `c` `linkList` `w1`