# 2021q1 Homework1 (lab0)
contributed by < `hankluo6` >
> 延續 [2020q3 Homework1(lab0)](https://hackmd.io/@hankluo6/2020q3-lab0), [2021q1 Homework1(lab0)](https://hackmd.io/@hankluo6/2021q1-lab0) 的開發
>
> [GitHub](https://github.com/hankluo6/lab0-c)
## 環境
```shell
$ gcc --version
gcc (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0
$ lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 1
On-line CPU(s) list: 0
Thread(s) per core: 1
Core(s) per socket: 1
Socket(s): 1
NUMA node(s): 1
Vendor ID: GenuineIntel
CPU family: 6
Model: 158
Model name: Intel(R) Core(TM) i5-8300H CPU @ 2.30GHz
Stepping: 10
CPU MHz: 2304.002
BogoMIPS: 4608.00
Hypervisor vendor: KVM
Virtualization type: full
L1d cache: 32K
L1i cache: 32K
L2 cache: 256K
L3 cache: 8192K
NUMA node0 CPU(s): 0
```
### Queue implementation
#### `q_new`
```c
struct list_head *q_new()
{
struct list_head *q = malloc(sizeof(struct list_head));
if (!q)
return NULL;
INIT_LIST_HEAD(q);
return q;
}
```
利用 `INIT_LIST_HEAD` 初始化 list_head 當作 queue 的 head。
#### `q_insert_head`
```c
bool q_insert_head(struct list_head *head, char *s)
{
if (!head)
return false;
element_t *e = malloc(sizeof(element_t));
if (!e)
return false;
e->value = strdup(s);
if (!e->value) {
free(e);
return false;
}
list_add(&e->list, head);
return true;
}
```
分配 `element_t` 的空間並將字串存入,再與 queue 連接。
#### `q_insert_tail`
```c
bool q_insert_tail(struct list_head *head, char *s)
{
if (!head)
return false;
element_t *e = malloc(sizeof(element_t));
if (!e)
return false;
e->value = strdup(s);
if (!e->value) {
free(e);
return false;
}
list_add_tail(&e->list, head);
return true;
}
```
同 `q_insert_head`。
#### `q_remove_head`
```c
element_t *q_remove_head(struct list_head *head, char *sp, size_t bufsize)
{
if (!head || list_empty(head))
return NULL;
element_t *e = list_entry(head->next, element_t, list);
list_del(head->next);
if (sp) {
strncpy(sp, e->value, bufsize - 1);
sp[bufsize - 1] = '\0';
}
return e;
}
```
利用 `list_entry` 取得第一個 element,並把資料放入 `sp`。
#### `q_remove_tail`
```c
element_t *q_remove_tail(struct list_head *head, char *sp, size_t bufsize)
{
if (!head || list_empty(head))
return NULL;
return q_remove_head(head->prev->prev, sp, bufsize);
}
```
取得 tail 的前一個元素,便能利用 `q_remove_head` 來移除。
#### `q_size`
```c
int q_size(struct list_head *head)
{
if (!head)
return 0;
int len = 0;
struct list_head *li;
list_for_each (li, head)
len++;
return len;
}
```
#### `q_delete_mid`
```c
bool q_delete_mid(struct list_head *head)
{
// https://leetcode.com/problems/delete-the-middle-node-of-a-linked-list/
if (!head || list_empty(head))
return false;
struct list_head *fast = head->next;
struct list_head *slow = head->next;
while (fast != head && fast->next != head) {
fast = fast->next->next;
slow = slow->next;
}
list_del(slow);
q_release_element(list_entry(slow, element_t, list));
return true;
}
```
運用 [Floyd's Algorithm](https://en.wikipedia.org/wiki/Cycle_detection#Floyd's_tortoise_and_hare) 的 fast and slow pointer 的方法找出中間元素,再移除即可。
#### `q_delete_dup`
```c
bool q_delete_dup(struct list_head *head)
{
// https://leetcode.com/problems/remove-duplicates-from-sorted-list-ii/
if (!head || list_empty(head))
return false;
struct list_head *e;
list_for_each (e, head) {
struct list_head *next = e->next;
bool dup = false;
while (next != head &&
!strcmp(list_entry(e, element_t, list)->value,
list_entry(next, element_t, list)->value)) {
list_del(e);
q_release_element(list_entry(e, element_t, list));
e = next;
next = e->next;
dup = true;
}
if (dup) {
list_del(e);
q_release_element(list_entry(e, element_t, list));
e = next;
}
}
return true;
}
```
遍歷整個 queue,並再迴圈中檢查當前元素與下一個元素是否相同,並持續移除下個元素,最後把當前元素也移除便能刪除所有相同的元素。
#### `q_swap`
```c
void q_swap(struct list_head *head)
{
// https://leetcode.com/problems/swap-nodes-in-pairs/
if (!head || list_empty(head))
return;
struct list_head *e;
list_for_each (e, head) {
if (e->next == head)
break;
struct list_head *next = e->next;
list_del(e);
list_add(e, next);
}
}
```
遍歷 queue 時兩兩交換即可。
#### `q_reverse`
```c
void q_reverse(struct list_head *head)
{
if (!head || list_empty(head))
return;
struct list_head *prev, *iter, *rear;
for (prev = head, iter = head->next, rear = iter->next; iter != head;) {
iter->prev = rear;
iter->next = prev;
prev = iter;
iter = rear;
rear = rear->next;
}
head->next = prev;
head->prev = rear;
}
```
利用三個 pointer 分別指向 prev, current, next 元素,在遍歷時持續更改指標指向。
#### `q_sort`
```c
void merge_sort(struct list_head **head)
{
if (!(*head) || !(*head)->next)
return;
struct list_head *fast = (*head)->next;
struct list_head *slow = *head;
while (fast && fast->next) {
fast = fast->next->next;
slow = slow->next;
}
fast = slow->next;
slow->next = NULL;
struct list_head **front = head;
struct list_head **back = &fast;
merge_sort(front);
merge_sort(back);
struct list_head *newh = NULL;
struct list_head **tmp = &newh;
while (*front && *back) {
element_t *f = list_entry(*front, element_t, list);
element_t *b = list_entry(*back, element_t, list);
if (strcmp(f->value, b->value) < 0) {
*tmp = *front;
*front = (*front)->next;
} else {
*tmp = *back;
*back = (*back)->next;
}
tmp = &((*tmp)->next);
}
if (*front)
*tmp = *front;
else if (*back)
*tmp = *back;
*head = newh;
}
void q_sort(struct list_head *head)
{
if (q_size(head) < 2)
return;
head->next->prev = NULL;
head->prev->next = NULL;
merge_sort(&head->next);
struct list_head *e, *prev;
for (e = head->next, prev = head;; prev = e, e = e->next) {
if (!e) {
head->prev = prev;
prev->next = head;
break;
}
e->prev = prev;
}
}
```
使用 merge sort 作為排序,使用 pointer to pointer 可以避免 merge 完需要回傳 node,在 merge 過程只有建立 `next` 連接,故在 `q_sort` 內需重新設定 `prev` 指標。
### Queue Shuffle
```c
void q_shuffle(struct list_head *head)
{
int len = q_size(head);
if (len <= 1)
return;
struct list_head *node, *tmp, *last = head->prev;
while (--len) {
int r = rand() % (len + 1);
list_for_each(node, head) {
if (!r)
break;
r--;
}
tmp = node->prev;
list_del(node);
list_add(node, last);
list_del(last);
list_add(last, tmp);
last = node->prev;
}
}
```
### Web Implementatiton
[先前方法](https://hackmd.io/@hankluo6/2021q1-lab0#%E6%95%B4%E5%90%88-tiny-web-server-%E8%87%B3-lab0)使用 `cmd_select` 接收資料,需要將 linenoise 關閉,且 `cmd_select` 似乎專門用於 source 讀檔專用,故改採用更改 linenoise 讀取的方式來接收 web 傳來的訊息。
```diff
static int linenoiseEdit(...)
{
...
linenoiseHistoryAdd("");
if (write(l.ofd, prompt, l.plen) == -1)
return -1;
while (1) {
signed char c;
int nread;
char seq[3];
+ if (listenfd != -1) {
+ fd_set set;
+ FD_ZERO(&set);
+ FD_SET(listenfd, &set);
+ FD_SET(stdin_fd, &set);
+ int rv = select(listenfd + 1, &set, NULL, NULL, NULL);
+ struct sockaddr_in clientaddr;
+ socklen_t clientlen = sizeof clientaddr;
+ int connfd;
+
+ switch (rv) {
+ case -1:
+ perror("select"); /* an error occurred */
+ continue;
+ case 0:
+ printf("timeout occurred\n"); /* a timeout occurred */
+ continue;
+ default:
+ if (FD_ISSET(listenfd, &set)) {
+ connfd = accept(listenfd,(SA *) &clientaddr, &clientlen);
+ char *p = process(connfd, &clientaddr);
+ strncpy(buf, p, strlen(p) + 1);
+ close(connfd);
+ free(p);
+ return strlen(p);
+ } else if (FD_ISSET(stdin_fd, &set)) {
+ nread = read(l.ifd, &c, 1);
+ if (nread <= 0)
+ return l.len;
+ }
+ break;
+ }
+ } else {
nread = read(l.ifd, &c, 1);
if (nread <= 0)
return l.len;
+ }
...
}
}
```
將 web 使用的 filedescription 以及 stdin 使用的 file desciption 都加到 `set` 當中,便能使用 [`select`](https://man7.org/linux/man-pages/man2/select.2.html) 判斷目前接收到的資料來源,`FD_ISSET` 判斷哪個 fd 被設置,如果為 `listenfd` 表示收到 web 端傳來的資料,如果為 `stdin_fd` 則表示為使用者輸入端傳來的資料。
TODO Coroutine
### termios
TODO
###### tags: `linux2022`