# 2021q1 Homework1 (lab0)
contributed by < `a12345645` >
:::danger
注意作業書寫規範!
:notes: jserv
:::
## 環境
```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): 8
On-line CPU(s) list: 0-7
Thread(s) per core: 2
Core(s) per socket: 4
Socket(s): 1
NUMA node(s): 1
Vendor ID: GenuineIntel
CPU family: 6
Model: 94
Model name: Intel(R) Core(TM) i7-6700 CPU @ 3.40GHz
Stepping: 3
CPU MHz: 3400.000
CPU max MHz: 4000.0000
CPU min MHz: 800.0000
BogoMIPS: 6799.81
Virtualization: VT-x
L1d cache: 128 KiB
L1i cache: 128 KiB
L2 cache: 1 MiB
L3 cache: 8 MiB
NUMA node0 CPU(s): 0-7
```
## 開發過程 queue.c
### q_new
```c
struct list_head *q_new()
{
struct list_head *q = malloc(sizeof(struct list_head));
if (q)
INIT_LIST_HEAD(q);
return q;
}
```
- 使用 `malloc` 分配一塊記憶體給 `list_head`
- 如果成功分配就會使用 [`INIT_LIST_HEAD`](#INIT_LIST_HEAD) 來初始化
### q_free
```c
void q_free(struct list_head *l)
{
if (l) {
element_t *e, *safe;
list_for_each_entry_safe (e, safe, l, list) {
q_release_element(e);
}
free(l);
}
}
```
- 如果 `l` 為 `NULL` 不做事
- 使用 [`list_for_each_entry_safe`](#list_for_each_entry_safe) 走訪所有元素
- 並使用[`q_release_element`](#q_release_element)刪除
#### q_release_element
```c
void q_release_element(element_t *e)
{
free(e->value);
free(e);
}
```
- 刪除 `element_t`
### q_insert_head
```c
bool q_insert_head(struct list_head *head, char *s)
{
if (!head)
return false;
element_t *item = q_alloc_element(s);
if (!item)
return false;
list_add(&item->list, head);
return true;
}
```
- 如果 `head` 為 `NULL` 回傳 `false`
- 使用 [`q_alloc_element`](#q_alloc_element) 分配記憶體給 `item` ,分配失敗則回傳 `false`
- 使用 [`list_add`](#list_add) 將 `item` 插入 `head` 的前端
#### q_alloc_element
```c
element_t *q_alloc_element(char *s)
{
element_t *e = malloc(sizeof(element_t));
if (!e)
return NULL;
e->value = strdup(s);
if (!e->value) {
free(e);
return NULL;
}
INIT_LIST_HEAD(&e->list);
return e;
}
```
- 使用 `malloc` 分配記憶體,如果失敗回傳 `NULL`
- 使用 `strdup` 分配記憶體給 `value` ,並複製進去,如果失敗回傳 `NULL`
- 使用 [`INIT_LIST_HEAD`](#INIT_LIST_HEAD) 初始化
### q_insert_tail
```c
bool q_insert_tail(struct list_head *head, char *s)
{
if (!head)
return false;
element_t *item = q_alloc_element(s);
if (!item)
return false;
list_add_tail(&item->list, head);
return true;
}
```
- 與 [`q_insert_head`](#q_insert_head) 大致相同
- 差別於使用 [`list_add_tail`](#list_add_tail) 將 `item` 插入 `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 *item = list_entry(head->next, element_t, list);
list_del_init(head->next);
if (sp) {
strncpy(sp, item->value, bufsize - 1);
sp[bufsize - 1] = '\0';
}
return item;
}
```
- 判斷 `head` 是 `NULL` 或是使用 [`list_empty`](#list_empty) 判斷為空佇列回傳 `NULL`
- `head->next`
- 使用 [`list_entry`](#list_entry) 取出 `element_t`
- 使用 [`list_del_init`](#list_del_init) 刪除並初始化節點
### q_size
```c
int q_size(struct list_head *head)
{
if (!head)
return 0;
struct list_head *node;
int count = 0;
list_for_each (node, head)
count++;
return count;
}
```
- 使用 [`list_for_each`](#list_for_each) 來歷遍所有節點並計數
### 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 *mid = head->next, *fast = head->next;
while (fast != head && fast->next != head) {
mid = mid->next;
fast = fast->next->next;
}
list_del(mid);
q_release_element(list_entry(mid, element_t, list));
return true;
}
```
- 用兩個指標一個快 `fast` 一個慢 `mid`
- 快的速度為慢的兩倍,所以只要 `fast` 到尾端的時, `mid` 就會位於中間
- 再使用 `list_del` 與 `q_release_element` 將 `mid` 從 list 中移除與釋放記憶體空間
:::warning
考慮到 circular doubly-linked list 的特性,可以怎樣改進實作?
:notes: jserv
:::
### 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 *ptr1 = head->next, *ptr2 = head->next->next;
while (ptr2 != head) {
bool flage = false;
element_t *node1, *node2;
node1 = list_entry(ptr1, element_t, list);
node2 = list_entry(ptr2, element_t, list);
while (!strcmp(node1->value, node2->value)) {
flage = true;
ptr2 = ptr2->next;
q_release_element(node2);
if (ptr2 == head)
break;
node2 = list_entry(ptr2, element_t, list);
}
if (flage) {
ptr1 = ptr1->prev;
q_release_element(node1);
}
ptr1->next = ptr2;
ptr2->prev = ptr1;
ptr1 = ptr2;
if (ptr2 != head)
ptr2 = ptr2->next;
}
return true;
}
```
- 有兩個指標 `ptr1`, `ptr2`
- `ptr1` 為前一個 `ptr2`
- 當兩個指標的 `value` 一樣的話, `flage` 就會立起並且 `ptr2` 就會繼續往前直到與 `ptr1` 的直不同或是到尾端。
- 使用 [`q_release_element`](#q_release_element) 刪除
- 如果 `flage` 被立起來 `ptr1` 就會往前一個並與 `ptr2` 相接。
### 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 *ptr = head->next;
while (ptr != head && ptr->next != head) {
list_swap(ptr, ptr->next);
ptr = ptr->next;
}
}
```
- 把 `ptr` 與 `ptr->next` 使用 `list_swap` 交換
- 因為 `ptr` 被換到前面了所以只需要在往下一個走一步就可以了
### q_reverse
```c
void q_reverse(struct list_head *head)
{
if (!head || list_empty(head))
return;
struct list_head *current = head->next, *tmp;
while (current != head) {
tmp = current->prev;
current->prev = current->next;
current->next = tmp;
current = current->prev;
}
tmp = head->next;
head->next = head->prev;
head->prev = tmp;
}
```
- 只要把原本的 `prev` 與 `next` 調換就可以反轉
### q_sort
```c
struct list_head *merge_sort(struct list_head *head)
{
if (!head || !head->next)
return head;
struct list_head *slow = head, *fast = head;
while (fast && fast->next) {
slow = slow->next;
fast = fast->next->next;
}
slow->prev->next = NULL;
struct list_head *left, *right, *merge, *ptr;
left = merge_sort(head);
right = merge_sort(slow);
element_t *node1, *node2;
node1 = list_entry(left, element_t, list);
node2 = list_entry(right, element_t, list);
if (strcmp(node1->value, node2->value) <= 0) {
merge = left;
left = left->next;
} else {
merge = right;
right = right->next;
}
ptr = merge;
while (left && right) {
node1 = list_entry(left, element_t, list);
node2 = list_entry(right, element_t, list);
if (strcmp(node1->value, node2->value) <= 0) {
ptr->next = left;
left = left->next;
} else {
ptr->next = right;
right = right->next;
}
ptr = ptr->next;
}
if (left) {
ptr->next = left;
}
if (right) {
ptr->next = right;
}
return merge;
}
void q_sort(struct list_head *head)
{
if (!head || !head->next)
return;
head->prev->next = NULL;
head->next = merge_sort(head->next);
struct list_head *front = head, *behind = head->next;
while (behind) {
behind->prev = front;
front = behind;
behind = behind->next;
}
front->next = head;
head->prev = front;
}
```
- 先斷開 double-linked 把 list 當作單向的來處理
- 所以在每一次遞迴都會做 `slow->prev->next = NULL` 來斷開雙向
- 再來就是一般的 merge sort 並在最後重現把 `prev` 連回去
## Functions in list.h
### INIT_LIST_HEAD
- Initialize an unlinked list node.
```c
static inline void INIT_LIST_HEAD(struct list_head *head)
{
head->next = head;
head->prev = head;
}
```
### list_for_each_entry_safe
- The current node (iterator) is allowed to be removed from the list.
- Any other modifications to the the list will cause undefined behavior.
```c
#define list_for_each_entry_safe(entry, safe, head, member) \
for (entry = list_entry((head)->next, __typeof__(*entry), member), \
safe = list_entry(entry->member.next, __typeof__(*entry), member); \
&entry->member != (head); entry = safe, \
safe = list_entry(safe->member.next, __typeof__(*entry), member))
```
## qtest.c
### shuffle
```c=
void q_shuffle(struct list_head *head)
{
if (!head || !head->next)
return;
int cnt = q_size(head);
srand(time(NULL));
for (int i = cnt; i > 0; i--) {
int index = rand() % i;
struct list_head *tmp = head->next;
for (int j = 0; j < index; j++)
tmp = tmp->next;
list_del(tmp);
list_add_tail(tmp, head);
}
}
static bool do_shuffle(int argc, char *argv[])
{
if (argc != 1) {
report(1, "%s takes no arguments", argv[0]);
return false;
}
if (!l_meta.l)
report(3, "Warning: Calling reverse on null queue");
error_check();
set_noallocate_mode(true);
if (exception_setup(true))
q_shuffle(l_meta.l);
exception_cancel();
set_noallocate_mode(false);
show_queue(3);
return !error_check();
}
```
參考了 `do_reverse` 來新增了新的 command
並把 shuffle 實做於 `q_shuffle`
- 隨機的取一個 `index`
- 並把位於那個位置的元素插入尾端