---
tags: linux2023
---
# 2023q1 Homework1 (lab0)
contributed by < `YSRossi` >
## 開發環境
```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
Address sizes: 39 bits physical, 48 bits virtual
CPU(s): 12
On-line CPU(s) list: 0-11
Thread(s) per core: 2
Core(s) per socket: 6
Socket(s): 1
NUMA node(s): 1
Vendor ID: GenuineIntel
CPU family: 6
Model: 158
Model name: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
Stepping: 10
CPU MHz: 2600.000
CPU max MHz: 4500.0000
CPU min MHz: 800.0000
BogoMIPS: 5199.98
Virtualization: VT-x
L1d cache: 192 KiB
L1i cache: 192 KiB
L2 cache: 1.5 MiB
L3 cache: 12 MiB
NUMA node0 CPU(s): 0-11
```
:::warning
`lscpu` 套件提供的中文翻譯品質不佳,請先執行 `export LC_ALL=C` 再執行 `lscpu`,隨後更新上述硬體描述。
:notes: jserv
> 已更新
:::
## 開發過程
### `q_new`
動態配置 head 所需的記憶體並使用 `INIT_LIST_HEAD` 對其進行初始化。
```c
/* Create an empty queue */
struct list_head *q_new()
{
struct list_head *head = malloc(sizeof(struct list_head));
if (head)
INIT_LIST_HEAD(head);
return head;
}
```
:::warning
將 `malloc(sizeof(struct list_head))` 改為 `malloc(sizeof(*head))` 以簡化程式碼。
:notes: jserv
> 已更新
:::
### `q_free`
一開始檢查 `l` 是否為空,如果為空就將 `l` 釋放。
為了刪掉目前的元素後還能夠走訪下個節點,所以選擇 `list_for_each_entry_safe` 來逐一走訪每個節點並將其所佔用空間釋放。
```c
/* Free all storage used by queue */
void q_free(struct list_head *l)
{
if (!l || list_empty(l)) {
free(l);
return;
}
element_t *entry = NULL, *safe = NULL;
list_for_each_entry_safe (entry, safe, l, list) {
list_del(&entry->list);
q_release_element(entry);
}
free(l);
}
```
### `q_insert_head`
配置一個 `s` 字串長度加一 (`'\0'`)的空間給 `entry->value`,並將 `s` 複製到 `entry->value`, 最後使用 `list_add` 將 `entry` 加入 list 的開頭。
```c
/* Insert an element at head of queue */
bool q_insert_head(struct list_head *head, char *s)
{
if(!head)
return false;
element_t *entry = malloc(sizeof(element_t));
if (!entry)
return false;
size_t len = strlen(s) + 1;
entry->value = malloc(len * sizeof(char));
if (!entry->value) {
free(entry);
return false;
}
memcpy(entry->value, s, len);
INIT_LIST_HEAD(&entry->list);
list_add(&entry->list, head);
return true;
}
```
:::warning
可以將`INIT_LIST_HEAD`移除,因為在下一行的list_add就會將entry->list的prev以及next配置
:notes: WilsonKuo
:::
### `q_insert_tail`
與 `q_insert_head` 作法相似,差在最後使用 `list_add_tail` 將 `entry` 加入 list 的尾端。
```c
/* Insert an element at tail of queue */
bool q_insert_tail(struct list_head *head, char *s)
{
if(!head)
return false;
element_t *entry = malloc(sizeof(element_t));
if(!entry)
return false;
size_t len = strlen(s) + 1;
entry->value = malloc(len * sizeof(char));
if(!entry->value) {
free(entry);
return false;
}
memcpy(entry->value, s, len);
INIT_LIST_HEAD(&entry->list);
list_add_tail(&entry->list, head);
return true;
}
```
### `q_remove_head`
使用 `list_first_entry` 找到第一個元素,並使用 `list_del_init` 將該元素從串列中移走,最後使用 strncpy 將先前移走的元素的 `value` 複製到 `sp` 中。
```c
/* Remove an element from head of queue */
element_t *q_remove_head(struct list_head *head, char *sp, size_t bufsize)
{
if(!head || list_empty(head))
return NULL;
element_t *entry = list_first_entry(head, element_t, list);
list_del_init(&entry->list);
if (sp) {
strncpy(sp, entry->value, bufsize - 1);
sp[bufsize - 1] = '\0';
}
return entry;
}
```
### `q_remove_tail`
與 `q_remove_head` 作法相似,差異是使用 `list_last_entry` 找到串列中最後一個 entry。
```c
/* Remove an element from tail of queue */
element_t *q_remove_tail(struct list_head *head, char *sp, size_t bufsize)
{
if(!head || list_empty(head))
return NULL;
element_t *entry = list_last_entry(head, element_t, list);
list_del_init(&entry->list);
if (sp) {
strncpy(sp, entry->value, bufsize - 1);
sp[bufsize - 1] = '\0';
}
return entry;
}
```
### `q_size`
使用 `list_for_each` 走訪整個佇列,使用 `size` 變數來紀錄總共的 elements 數量。
```c
/* Return number of elements in queue */
int q_size(struct list_head *head)
{
if (!head)
return -1;
int size = 0;
struct list_head *node = NULL;
list_for_each (node, head)
size++;
return size;
}
```
### `q_delete_mid`
正向 : 往 `next` 的方向。
反向 : 往 `prev` 的方向。
從 `head->next` 與 `head->prev` 分別往正向與反向走訪,直到雙方目前走訪的節點相同或是相鄰,即找到中間節點,將該點刪除並釋放。
```c
/* Delete the middle node in queue */
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 *forward = head->next, *backward = head->prev;
while (forward != backward && forward->next != backward) {
forward = forward->next;
backward = backward->prev;
}
list_del_init(forward);
element_t *entry = list_entry(forward, element_t, list);
q_release_element(entry);
return true;
}
```
### `q_delete_dup`
想法是目前節點是否與上個節點的 `value` 相同來決定要不要刪除上個節點,所以一串擁有相同 `value` 的節點當中的最後一個節點不會被刪除,因此使用 `del` 紀錄目前節點走訪到下個節點時是否也要刪除。
```c
/* Delete all nodes that have duplicate string */
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;
element_t *entry = NULL, *safe = NULL, *dentry = NULL;
char *val = "";
bool del = false;
list_for_each_entry_safe (entry, safe, head, list) {
if (!strcmp(entry->value, val)) {
if (list_last_entry(head, element_t, list) == entry) {
list_del(&entry->list);
q_release_element(entry);
}
list_del(&dentry->list);
q_release_element(dentry);
del = true;
} else {
if (del) {
list_del(&dentry->list);
q_release_element(dentry);
}
val = entry->value;
del = false;
}
dentry = entry;
}
return true;
}
```
### `q_swap`
因為 `list_move` 為 `list_del` 與 `list_add` 的組合,因為 `list_move` 會將目標串列的開頭做一次 `next`,即 `list_move(node, node->next)` 相當於將 `node` 接到 `node->next-next` 的前面,所以與 `list_for_each` 一起使用剛好可以使串列兩兩交換。
```c
/* Swap every two adjacent nodes */
void q_swap(struct list_head *head)
{
// https://leetcode.com/problems/swap-nodes-in-pairs/
if (!head || list_empty(head))
return;
struct list_head *node = NULL;
list_for_each (node, head) {
if (node->next == head)
break;
list_move(node, node->next);
}
}
```
### `q_reverse`
將串列每個節點的 `next` 與 `prev` 交換。
```c
/* Reverse elements in queue */
void q_reverse(struct list_head *head)
{
struct list_head *node = NULL, *safe = NULL;
list_for_each_safe (node, safe, head) {
struct list_head *tmp = node->next;
node->next = node->prev;
node->prev = tmp;
}
struct list_head *tmp = head->next;
node->next = head->prev;
node->prev = tmp;
}
```
### q_reverseK
每走訪 K 個節點,將這 K 個走訪過的節點使用 `list_cut_position` 移到 `tmp` 做 `q_reverse` 後,使用 `list_splice_init` 插入原本的串列。
```c
/* Reverse the nodes of the list k at a time */
void q_reverseK(struct list_head *head, int k)
{
// https://leetcode.com/problems/reverse-nodes-in-k-group/
if (!head || list_empty(head))
return;
int cnt = 0;
struct list_head *node = NULL, *safe = NULL, *from = head;
LIST_HEAD(tmp);
list_for_each_safe (node, safe, head) {
cnt++;
if (cnt == k) {
list_cut_position(&tmp, from, node);
q_reverse(&tmp);
list_splice_init(&tmp, from);
from = safe->prev;
cnt = 0;
}
}
}
```
### q_sort
原本思考如何利用與 `q_delete_mid` 一樣分別從頭與尾往中間走訪找中間節點的方式,搭配[你所不知道的 C 語言: linked list 和非連續記憶體](https://hackmd.io/@sysprog/c-linked-list)中介紹的遞迴版本 merge sort 來實作,考量到過程會常常切割串列,為了能快速找到 `tail` ,還需維護 `prev` ,因此最後選擇用快慢指標尋找中間節點,排序完成後再將串列變回雙向環狀鏈結串列。
```c
static struct list_head *merge_two_lists(struct list_head *L1,
struct list_head *L2)
{
struct list_head *head = NULL, **ptr = &head, **node;
for (node = NULL; L1 && L2; *node = (*node)->next) {
element_t *node1 = list_entry(L1, element_t, list);
element_t *node2 = list_entry(L2, element_t, list);
node = (strcmp(node1->value, node2->value) <= 0) ? &L1 : &L2;
*ptr = *node;
ptr = &(*ptr)->next;
}
*ptr = (struct list_head *) ((uintptr_t) L1 | (uintptr_t) L2);
return head;
}
static struct list_head *merge_sort(struct list_head *head)
{
if (!head || !head->next)
return head;
struct list_head *fast, *slow = head;
for (fast = head->next; fast && fast->next; fast = fast->next->next)
slow = slow->next;
struct list_head *mid = slow->next;
slow->next = NULL;
struct list_head *left = merge_sort(head), *right = merge_sort(mid);
return merge_two_lists(left, right);
}
/* Sort elements of queue in ascending order */
void q_sort(struct list_head *head)
{
if (!head || list_empty(head) || list_is_singular(head))
return;
head->prev->next = NULL;
head->next = merge_sort(head->next);
struct list_head *ptr;
for (ptr = head; ptr->next; ptr = ptr->next)
ptr->next->prev = ptr;
ptr->next = head;
head->prev = ptr;
}
```
### q_descend
從串列尾端 `head->prev` 往 `prev` 方向走訪每個節點,過程中紀錄到目前為止走過的節點的最大值 `max` ,並刪除比 `max` 小的節點。
```c
/* Remove every node which has a node with a strictly greater value anywhere to
* the right side of it */
int q_descend(struct list_head *head)
{
// https://leetcode.com/problems/remove-nodes-from-linked-list/
if (!head || list_empty(head))
return 0;
int total = 0, rm = 0;
char *max = list_last_entry(head, element_t, list)->value;
element_t *entry = NULL, *safe = NULL;
for (entry = list_entry((head)->prev, element_t, list),
safe = list_entry(entry->list.prev, element_t, list);
&entry->list != (head);
entry = safe, safe = list_entry(safe->list.prev, element_t, list)) {
total++;
if (strcmp(entry->value, max) < 0) {
list_del(&entry->list);
q_release_element(entry);
rm++;
} else
max = entry->value;
}
return total - rm;
}
```
### q_merge
剛開始的思考方向是如何找到其他佇列的 `head` ,後來在 `queue.h` 找到了管理佇列的 `queue_contex_t`,如此一來就可以使用目前已知的 `head` 搭配 `list_for_each_entry` 走訪每個佇列,將每個已排序的佇列接在一起並排序。
```c
/* Merge all the queues into one sorted queue, which is in ascending order */
int q_merge(struct list_head *head)
{
// https://leetcode.com/problems/merge-k-sorted-lists/
if (!head)
return 0;
LIST_HEAD(tmp);
queue_contex_t *qchain;
list_for_each_entry (qchain, head, chain)
list_splice_init(qchain->q, &tmp);
int size = q_size(&tmp);
q_sort(&tmp);
list_splice(&tmp, list_first_entry(head, queue_contex_t, chain)->q);
return size;
}
```