owned this note
owned this note
Published
Linked with GitHub
# 2023q1 Homework1 (lab0)
contributed by < [cin-cout](https://github.com/cin-cout) >
:::danger
未見 [cin-cout/lab0-c](https://github.com/cin-cout/lab0-c) 程式碼更新。
:notes: jserv
:::
## 開發環境
```shell
$ gcc --version
gcc (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.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: 1201.821
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
```
## 開發過程
### q_new
#### version 1
<s>很多人會</s> 宣告 element 再傳其中的 head 給 qtest,但其實 qtest 是<s>餵給</s> q_context_t 內的 q ,所以 malloc element 的大小只會浪費空間,free 也會需要多 free head 所在的 element。
:::danger
避免說「餵給」這樣不精準的話。「很多人」如何如何,到底跟你的開發進度何關?
:notes: jserv
:::
```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;
}
```
### q_insert_head
#### version 1
```c
bool q_insert_head(struct list_head *head, char *s)
{
element_t *q = malloc(sizeof(element_t));
if (!q)
return false;
strcpy(q->value, s);
q->list.prev = NULL;
q->list.next = head;
head->prev = &q->list;
}
```
#### version 2
<s>一開始耍白痴</s>,忘記剛創立時 q->value 是 NULL ,不能 copy 。
加了空間宣告。
:::warning
避免帶有個人色彩的發言,改進你的漢語表達。
:notes: jserv
:::
```c
bool q_insert_head(struct list_head *head, char *s)
{
element_t *q = malloc(sizeof(element_t));
if (!q) {
return false;
}
q->value = malloc(sizeof(s));
strcpy(q->value, s);
list_add(&q->list, head);
return true;
}
```
#### version 3
評分時發現11 12怎麼過不了明明已經做完了 insert 了,後來發現是動態宣告 value 的時候沒有檢查是否有足夠空間。
養成良好 coding 習慣,要檢查任何:
1. 傳入值是否有意義
2. 動態宣告是否有成功
```c
bool q_insert_head(struct list_head *head, char *s)
{
if(!head || !s){
return false;
}
element_t *q = malloc(sizeof(element_t));
if (!q) {
return false;
}
q->value = malloc(sizeof(s));
if (!(q->value)) {
free(q);
return false;
}
strcpy(q->value, s);
list_add(&q->list, head);
return true;
}
```
#### version 4
之前的方式是切跟 char s 一樣的 size ,但其實只要切跟字串一樣剛剛好大小就好了。
```c
bool q_insert_head(struct list_head *head, char *s)
{
if(!head){
return false;
}
element_t *q = malloc(sizeof(element_t));
if (!q) {
return false;
}
int s_len = strlen(s);
q->value = malloc(s_len+1);
if (!(q->value)) {
test_free(q);
return false;
}
strncpy(q->value, s, s_len);
q->value[s_len] = '\0';
list_add(&q->list, head);
return true;
}
```
### q_insert_tail
#### version 1
這是跟 insert head 一起做的,前者成功, insert tail <s>照本宣科</s>。
:::warning
查閱教育部辭典「[照本宣科](https://dict.revised.moe.edu.tw/dictView.jsp?ID=115527)」的寓意,再回來看這裡程式碼的撰寫,看用語是否恰當。
:notes: jserv
:::
```c
bool q_insert_tail(struct list_head *head, char *s)
{
if(!head){
return false;
}
element_t *q = malloc(sizeof(element_t));
if (!q) {
return false;
}
int s_len = strlen(s);
q->value = malloc(s_len+1);
if (!(q->value)) {
test_free(q);
return false;
}
strncpy(q->value, s, s_len);
q->value[s_len] = '\0';
list_add_tail(&q->list, head);
return true;
}
}
```
:::warning
思考如何精簡程式碼。
:notes: jserv
:::
### q_remove_head
#### version 1
```c
element_t *q_remove_head(struct list_head *head, char *sp, size_t bufsize)
{
if(!head || !sp){
return NULL;
}
element_t * cur_r = list_entry(head->next, element_t, list);
list_del_init(head->next);
strncpy(sp, cur_r->value, bufsize);
return cur_r;
}
```
#### version 2
加了 sp[bufsize-1] = '\0'; ,其實不影響成績,但是這是考慮若 value size > bufsize 時在最底端會沒有 '\0' 結束符號,所以加了這行。
```c
element_t *q_remove_head(struct list_head *head, char *sp, size_t bufsize)
{
if(!head || !sp){
return NULL;
}
element_t* cur_r = list_entry(head->next, element_t, list);
list_del_init(head->next);
strncpy(sp, cur_r->value, bufsize);
sp[bufsize-1] = '\0';
return cur_r;
}
```
#### version 3
參考之前範例,在 empty 下不會把 head 刪掉。
```c
element_t *q_remove_head(struct list_head *head, char *sp, size_t bufsize)
{
if(!head || !sp || list_empty(head)){
return NULL;
}
element_t* cur_r = list_entry(head->next, element_t, list);
list_del_init(head->next);
strncpy(sp, cur_r->value, bufsize);
sp[bufsize-1] = '\0';
return cur_r;
}
```
#### version 4
參考 [chun61205](https://hackmd.io/@roger61205/lab0-2023) ,原作法對 sp 的處理有問題,若 sp 傳 NULL 只代表 test 不需要用到 sp 來做一些輸出,還是要 remove node ,原作法是將 sp == NULL 視為無效。
```c
element_t *q_remove_head(struct list_head *head, char *sp, size_t bufsize)
{
if(!head || list_empty(head)){
return NULL;
}
element_t* cur_r = list_entry(head->next, element_t, list);
list_del_init(head->next);
if(sp){
strncpy(sp, cur_r->value, bufsize-1);
sp[bufsize-1] = '\0';
}
return cur_r;
}
```
### q_remove_tail
#### version 1
同上,<s>照本宣科</s>
```c
element_t *q_remove_tail(struct list_head *head, char *sp, size_t bufsize)
{
if(!head || list_empty(head)){
return NULL;
}
element_t* cur_r = list_entry(head->prev, element_t, list);
list_del_init(head->prev);
if(sp){
strncpy(sp, cur_r->value, bufsize-1);
sp[bufsize-1] = '\0';
}
return cur_r;
}
```
### q_delete_mid
#### version 1
最後 error,time limit exceeded。
```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* slow = head->next;
struct list_head* fast = head->next;
while(fast != head || fast != head->prev){
slow = slow->next;
fast = fast->next->next;
}
//del slow
list_del_init(slow);
q_free(slow);
return true;
}
```
#### version 2
while 條件打錯了,應該是&&不是||
```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* slow = head->next;
struct list_head* fast = head->next;
while(fast != head && fast != head->prev){
slow = slow->next;
fast = fast->next->next;
}
//del slow
list_del_init(slow);
q_free(slow);
return true;
}
```
### q_size
#### version 1
這很簡單,原本想試看看可不可以類似 container_of 的作法直接去找 queue_context_t 的 size ,但以失敗告終。
```c
/* Return number of elements in queue */
int q_size(struct list_head *head)
{
if(!head){
return 0;
}
int len = 0;
struct list_head *node;
list_for_each(node, head)
len++;
return len;
}
```
### q_free
#### version 1
```c
void q_free(struct list_head *l) {
struct list_head *node;
struct list_head *safe;
element_t* cur_f;
list_for_each_safe(node, safe, l){
cur_f = list_entry(node, element_t, list);
free(cur_f);
}
INIT_LIST_HEAD(l);
}
```
#### version 2
加了防呆,目前認為以前有些人的 queue free 做的有問題。
在 qtest 內 head 是放在 queue_context_t 裡面,而 qtest 再 q_free 後也會把 queue_context_t 給 free 掉,所以 qfree 應該是不需要處理 head 的。
:::warning
改進漢語表達。
:notes: jserv
:::
```c
void q_free(struct list_head *l)
{
if(!l){
return;
}
struct list_head *node;
struct list_head *safe;
element_t* cur_f;
list_for_each_safe(node, safe, l){
cur_f = list_entry(node, element_t, list);
free(cur_f);
}
}
```
#### version 3
用 q_release_element 會比較快,而且舊方法沒有刪掉 value 值。
```c
void q_free(struct list_head *l)
{
if(!l){
return;
}
struct list_head *node;
struct list_head *safe;
element_t* cur_f;
list_for_each_safe(node, safe, l){
cur_f = list_entry(node, element_t, list);
q_release_element(cur_f);
}
}
```
#### version 4
清掉 head 。
```c
void q_free(struct list_head *l)
{
if(!l){
return;
}
struct list_head *node;
struct list_head *safe;
element_t* cur_f;
list_for_each_safe(node, safe, l){
cur_f = list_entry(node, element_t, list);
q_release_element(cur_f);
}
//cur_f = list_entry(l, element_t, list);
test_free(l);
}
```
### q_reverse
#### version 1
用 for_each_safe 不用擔心更改以後 next 會不照原本方向,所以就是把每個點都 prev next 交換。
```c
void q_reverse(struct list_head *head)
{
struct list_head *node;
struct list_head *safe;
struct list_head *tmp;
list_for_each_safe(node, safe, head){
tmp = node->prev;
node->prev = node->next;
node->next = tmp;
}
}
```
#### version 2
原方法沒有處理到 head。
```c
void q_reverse(struct list_head *head)
{
if(!head){
return;
}
struct list_head *node;
struct list_head *safe;
struct list_head *tmp;
list_for_each_safe(node, safe, head){
tmp = node->prev;
node->prev = node->next;
node->next = tmp;
}
tmp = head->prev;
head->prev = head->next;
head->next = tmp;
}
```
### q_sort
#### version 1
參考 [komark06](https://hackmd.io/@komark06/SyCUIEYpj#q_merge-%E5%AF%A6%E4%BD%9C) 對原本雙向的 list 為了配合上課所學單向 merge sort 所作的修改,以及[上課講義](https://hackmd.io/@sysprog/c-linked-list#Merge-Sort-%E7%9A%84%E5%AF%A6%E4%BD%9C)對 sort list 的講解,在加上自己的一些<s>優化</s> 改進。
```c
struct list_head* merge_two_queue(struct list_head *L1, struct list_head *L2)
{
struct list_head* head, **ptr = &head, **node;
for (node = NULL; L1 && L2; *node = (*node)->next) {
node = (strcmp(list_entry(L1, element_t, list)->value, list_entry(L2, element_t, list)->value) >= 0) ? &L2: &L1;
*ptr = *node;
ptr = &(*ptr)->next;
}
*ptr = (struct list_head *)(void *)((uintptr_t)(void *) L1 | (uintptr_t)(void *) L2);
return head;
}
static struct list_head *merge_sort(struct list_head *head)
{
if(!head || !head->next){
return head;
}
struct list_head *slow = head, *fast = head->next;
for (; fast && fast->next; fast = fast->next->next, slow = slow->next)
;
fast = slow->next;
slow->next = NULL;
return merge_two_queue(merge_sort(head), merge_sort(fast));
}
/* Sort elements of queue in ascending order */
void q_sort(struct list_head *head)
{
if(!head || list_empty(head)){
return;
}
head->prev->next = NULL;
head->next = merge_sort(head->next);
struct list_head* tmp;
for(tmp=head; tmp->next; tmp=tmp->next){
tmp->next->prev = tmp;
}
tmp->next = head;
head->prev = tmp;
}
```
### q_merge
#### version 1
參考 [komark06](https://hackmd.io/@komark06/SyCUIEYpj#q_merge-%E5%AF%A6%E4%BD%9C) 對 queue context 資料結構的理解,以及[上課講義](https://hackmd.io/@sysprog/c-linked-list#Merge-Sort-%E7%9A%84%E5%AF%A6%E4%BD%9C)對 merge k sorted list 的講解,改進原本 [komark06](https://hackmd.io/@komark06/SyCUIEYpj#q_merge-%E5%AF%A6%E4%BD%9C)改為每看一個 queue 就 merge sort 一次,並非先全部<s>串街</s> ??? 再 merge sort。
```c
int q_merge(struct list_head *head)
{
// https://leetcode.com/problems/merge-k-sorted-lists/
if (!head || list_empty(head))
return 0;
queue_contex_t *f_q = list_entry(head->next, queue_contex_t, chain);
if (list_is_singular(head))
return f_q->size;
queue_contex_t *cur_q;
list_for_each_entry(cur_q, head, chain) {
if (cur_q != f_q) {
q_merge_two_sorted(f_q->q, cur_q->q);
f_q->size += cur_q->size;
cur_q->size = 0;
}
}
return f_q->size;
}
````
:::danger
程式碼的縮排和風格,務必依循 [CONTRIBUTING.md](https://github.com/sysprog21/lab0-c/blob/master/CONTRIBUTING.md) 規範。
:notes: jserv
:::
#### version 2
前一版沒有考慮使用 merge_two_queue 時 queue 必須先做前後處理。
```diff
@@ -7,12 +7,21 @@ int q_merge(struct list_head *head)
if (list_is_singular(head))
return f_q->size;
queue_contex_t *cur_q;
+ f_q->q->prev->next = NULL;
list_for_each_entry(cur_q, head, chain) {
if (cur_q != f_q) {
- q_merge_two_sorted(f_q->q, cur_q->q);
+ cur_q->q->prev->next = NULL;
+ f_q->q->next = merge_two_queue(f_q->q->next, cur_q->q->next);
+ INIT_LIST_HEAD(cur_q->q);
f_q->size += cur_q->size;
cur_q->size = 0;
}
}
+ struct list_head* tmp;
+ for(tmp=f_q->q; tmp->next; tmp=tmp->next){
+ tmp->next->prev = tmp;
+ }
+ tmp->next = f_q->q;
+ f_q->q->prev = tmp;
return f_q->size;
}
```
:::info
提示: 使用 `diff -up` 命令可產生二個檔案間的變更處列表
:notes: jserv
:::
### q_reverseK
#### version 1
```c
void q_reverseK(struct list_head *head, int k)
{
// https://leetcode.com/problems/reverse-nodes-in-k-group/
if(!head || list_empty(head)){
return;
}
struct list_head *node, *safe, *tmp_head, *tmp;
int cur_k = 1;
list_for_each_safe(node, safe, head){
if(k==1){
for (int i = 1; i < k; i++) {
tmp = node->next;
if(tmp == head){
return;
}
}
tmp_head = node;
}
else{
if(cur_k == k){
cur_k=0;
}
else{
cur_k++;
}
list_move(node, tmp_head);
}
}
```
#### version 2
cur_k 條件判斷有誤,然後 move 邏輯修正。
```c
void q_reverseK(struct list_head *head, int k)
{
// https://leetcode.com/problems/reverse-nodes-in-k-group/
if(!head || list_empty(head)){
return;
}
struct list_head *node, *safe, *tmp_head = head->next, *tmp;
int cur_k = 1;
list_for_each_safe(node, safe, head){
if(cur_k==1){
tmp = node;
for (int i = 1; i < k; i++) {
tmp = tmp->next;
if(tmp == head){
return;
}
}
tmp_head = node;
}
else{
list_move_tail(node, tmp_head);
tmp_head = node;
}
if(cur_k == k){
cur_k=1;
}
else{
cur_k++;
}
}
}
```
### q_delete_dup
#### version 1
```c
bool q_delete_dup(struct list_head *head)
{
// https://leetcode.com/problems/remove-duplicates-from-sorted-list-ii/
if(!head){
return false;
}
struct list_head *node;
bool last_del = 0;
list_for_each(node, head){
if(strcmp(list_entry(node, element_t, list)->value, list_entry(node->next, element_t, list)->value)){
if(last_del){
last_del = 0;
node = node->prev;
list_del(node->next);
q_release_element(list_entry(node->next, element_t, list));
}
}
else{//same
list_del(node->next);
q_release_element(list_entry(node->next, element_t, list));
node = node->prev;
last_del = 1;
}
}
return true;
}
```
#### version 2
```c
bool q_delete_dup(struct list_head *head)
{
// https://leetcode.com/problems/remove-duplicates-from-sorted-list-ii/
if(!head){
return false;
}
struct list_head *cur = head;
while(cur->next != head && cur->next->next != head){
element_t *c = list_entry(cur->next, element_t, list),
*n = list_entry(cur->next->next, element_t, list);
if(!strcmp(c->value, n->value)){
while(cur->next->next != head && !strcmp(c->value, n->value)){
struct list_head *next = n->list.next;
list_del(cur->next->next);
q_release_element(n);
n = list_entry(next, element_t, list);
}
list_del(cur->next);
q_release_element(c);
}
cur = cur->next;
}
return true;
}
```