owned this note
owned this note
Published
Linked with GitHub
---
tags: linux2022
---
# 2022q1 Homework1 (lab0)
> contributed by <[Linchi1997yeh](https://github.com/Linchi1997yeh)>
> [Link to homework1](https://hackmd.io/@sysprog/linux2022-lab0)
### Reviewed by `kevinshieh0225`
- To detect whether the list is empty, use `list_empty` in linux kernel api.
- If you found bug, you should find it and send patch to the origin repositary.
- `q_delete_dup` to many additional condition in the function, it can still be more concise.
- The implementation of `q_delete_dup` seems wrong, although you past the `make test`, try to fix the function to meet the expectation.
- To swap node in `shuffle`, use `list_move_tail` instead of swapping the char value.
## Environment
```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: 1
Core(s) per socket: 8
Socket(s): 1
NUMA node(s): 1
Vendor ID: GenuineIntel
CPU family: 6
Model: 158
Model name: Intel(R) Core(TM) i7-9700 CPU @ 3.00GHz
Stepping: 13
CPU MHz: 1058.944
CPU max MHz: 4700.0000
CPU min MHz: 800.0000
BogoMIPS: 6000.00
Virtualization: VT-x
L1d cache: 256 KiB
L1i cache: 256 KiB
L2 cache: 2 MiB
L3 cache: 12 MiB
NUMA node0 CPU(s): 0-7
```
## Requirements
The task is implement a queue (using linked list) with the following functions:
* `q_new`: 建立新的「空」佇列;
* `q_free`: 釋放佇列所佔用的記憶體;
* `q_insert_head`: 在佇列開頭 (head) 插入 (insert) 給定的新節點 (以 LIFO 準則);
* `q_insert_tail`: 在佇列尾端 (tail) 插入 (insert) 給定的新節點 (以 FIFO 準則);
* `q_remove_head`: 在佇列開頭 (head) 移去 (remove) 給定的節點;
* `q_release_element`: Release the memory used by a node in the queue.
* `q_size`: Return the number of nodes contained by the queue.
* `q_delete_mid`: 移走佇列中間的節點,詳見 [LeetCode 2095. Delete the Middle Node of a Linked List](https://leetcode.com/problems/delete-the-middle-node-of-a-linked-list/)
* `q_delete_dup`: 在已經排序的狀況,移走佇列中具備重複內容的節點,詳見 [LeetCode 82. Remove Duplicates from Sorted List II](https://leetcode.com/problems/remove-duplicates-from-sorted-list-ii/)
* `q_swap`: 交換佇列中鄰近的節點,詳見 [LeetCode 24. Swap Nodes in Pairs](https://leetcode.com/problems/swap-nodes-in-pairs/)
* `q_reverse`: 以反向順序重新排列鏈結串列,該函式不該配置或釋放任何鏈結串列節點,換言之,它只能重排已經存在的節點;
* `q_sort`: 以==遞增順序==來排序鏈結串列的節點,可參閱 [Linked List Sort](https://npes87184.github.io/2015-09-12-linkedListSort/) 得知實作手法;
## Development Journal
### q_new
Initialize a empty queue using list_head
* Wasn't too sure of the structure of ircular doubly linked list, So I made the mistake of creating a node and used the node as list head
```c=
struct list_head *q_new()
{
struct list_head *head = malloc(sizeof(struct list_head));
// if space cannot be allocated malloc will return NULL resulting into a
// NULL pointer
if (head) {
INIT_LIST_HEAD(head);
}
return head;
}
```
We can omit the case when memory cannot be allocated, because malloc returns Null if fail to allocate.
### q_free
Free all storage used by queue by using macros defined in lish.h
Here we need to use list_for_each_safe, to allow deletes. Here we pass in an extra pointer, so it can point to the next node to be iterated before it gets removed.
```c=
void q_free(struct list_head *l)
{
if (!l) {
return;
}
struct list_head *entry, *safe;
list_for_each_safe (entry, safe, l) {
element_t *node = container_of(entry, element_t, list);
q_release_element(node);
}
free(l);
}
```
### q_insert_head / q_insert_tail
Here we should be careful to free element_t node if the string memory space cannot be allocated to prevent memory leak problem.
:::warning
Here I encounter with a problem when pushing to git:
When calling line 18
list_add(&(node->list), head); // This would lead to memory leak problem
list_add(&node->list, head);
by just memoving the parenthesis the memory leak problem is not detected
:::
:::spoiler q_insert_head (code)
```c=
bool q_insert_head(struct list_head *head, char *s)
{
if (!head) {
return false;
}
element_t *node = malloc(sizeof(element_t));
if (!node) {
return false;
}
int strsize = strlen(s);
node->value = malloc(strsize + 1);
if (!(node->value)) {
free(node);
return false;
}
memcpy(node->value, s, strsize);
node->value[strsize] = '\0';
list_add(&node->list, head);
// list_add(&(node->list), head); why does this results into a memleak???
return true;
}
```
:::
:::spoiler q_insert_tail(code)
```c=
bool q_insert_tail(struct list_head *head, char *s)
{
if (!head) {
return false;
}
element_t *node = malloc(sizeof(element_t));
if (!node) {
return false;
}
int strsize = strlen(s);
node->value = malloc(strsize + 1);
if (!(node->value)) {
free(node);
return false;
}
memcpy(node->value, s, strsize);
node->value[strsize] = '\0';
list_add_tail(&node->list, head);
return true;
}
```
:::
### q_remove_head/q_remove_tail
Here the challenge was to return the value of the removed node.
First implementation I got the instructions wrong, if the buffsize is smaller than the value string I would not return anything.
::: spoiler q_remove_head
```c=
element_t *q_remove_head(struct list_head *head, char *sp, size_t bufsize)
{
if (!head || head->next == head) {
return NULL;
}
struct list_head *rm = head->next;
element_t *node = container_of(rm, element_t, list);
list_del(rm);
if (node->value != NULL) {
if (sp) {
memcpy(sp, node->value, bufsize - 1);
sp[bufsize - 1] = '\0';
}
}
return node;
}
```
:::
::: spoiler q_remove_tail
```c=
element_t *q_remove_tail(struct list_head *head, char *sp, size_t bufsize)
{
if (!head || head->next == head) {
return NULL;
}
struct list_head *rm = head->prev;
element_t *node = container_of(rm, element_t, list);
list_del(rm);
if (node->value != NULL) {
if (sp) {
memcpy(sp, node->value, bufsize - 1);
sp[bufsize - 1] = '\0';
}
}
return node;
}
```
:::
### q_size
Use list_for_each to iterate and count the number of nodes
```c=
int q_size(struct list_head *head)
{
if (!head) {
return 0;
}
int size = 0;
struct list_head *node;
list_for_each (node, head) {
size++;
}
return size;
}
```
### q_delete_mid
Here I implemented the turtle and hare concept.
Where there is two pointers hopping one-node and two-node per iteration respectively. When the faster pointer reaches head it means that the slow pointer is in the round down middle.
```c=
bool q_delete_mid(struct list_head *head)
{
// https://leetcode.com/problems/delete-the-middle-node-of-a-linked-list/
if (!head || head->next == head) {
return false;
}
struct list_head *iter1 = head->next, *iter2 = head->next->next;
while (iter2 != head && iter2->next != head) {
iter1 = iter1->next;
iter2 = iter2->next->next;
}
element_t *delnode = container_of(iter1, element_t, list);
list_del(iter1);
q_release_element(delnode);
return true;
}
```
### q_delete_dup
This can be further improved.
The idea is to delete nodes if the values of the previous nodes already appeared once.
-> Just found out that I got the function wrong when documenting but the test cases didn't contain this exception.
```c=
bool q_delete_dup(struct list_head *head)
{
// https://leetcode.com/problems/remove-duplicates-from-sorted-list-ii/
if (!head) {
return false;
}
element_t *entry, *safe;
char *laststring = "";
list_for_each_entry_safe (entry, safe, head, list) {
// compare if previous values appeared atleast once already
if (strcmp(entry->value, laststring) == 0) {
list_del(&entry->list);
q_release_element(entry);
} else { // here the values are seen for the first time
if (entry->list.prev != head) {
element_t *first =
container_of(entry->list.prev, element_t, list);
list_del(entry->list.prev);
q_release_element(first);
}
laststring = entry->value;
}
}
entry = list_last_entry(head, element_t, list);
if (strcmp(entry->value, laststring) == 0) {
list_del(&entry->list);
q_release_element(entry);
}
return true;
}
```
:::danger
Here is an example of wrong execution, but still got 100 on make test~.
:::
```
cmd> new
l = []
cmd> ih k 2
l = [k k]
cmd> ih j
l = [j k k]
cmd> ih c 2
l = [c c j k k]
cmd> dedup
l = []
cmd>
Freeing queue
darren@darren-Altos-P30-F6:~/linux2022/lab0-c$ make test
scripts/driver.py -c
--- Trace Points
+++ TESTING trace trace-01-ops:
# Test of insert_head and remove_head
--- trace-01-ops 5/5
+++ TESTING trace trace-02-ops:
# Test of insert_head, insert_tail, remove_head, remove_tail, and delete_mid
--- trace-02-ops 6/6
+++ TESTING trace trace-03-ops:
# Test of insert_head, insert_tail, reverse, and remove_head
--- trace-03-ops 6/6
+++ TESTING trace trace-04-ops:
# Test of insert_head, insert_tail, size, swap, and sort
--- trace-04-ops 6/6
+++ TESTING trace trace-05-ops:
# Test of insert_head, insert_tail, remove_head, reverse, size, swap, and sort
--- trace-05-ops 6/6
+++ TESTING trace trace-06-ops:
# Test of insert_head, insert_tail, delete duplicate, and sort
--- trace-06-ops 6/6
+++ TESTING trace trace-07-string:
# Test of truncated strings
--- trace-07-string 6/6
+++ TESTING trace trace-08-robust:
# Test operations on empty queue
--- trace-08-robust 6/6
+++ TESTING trace trace-09-robust:
# Test remove_head with NULL argument
--- trace-09-robust 6/6
+++ TESTING trace trace-10-robust:
# Test operations on NULL queue
--- trace-10-robust 6/6
+++ TESTING trace trace-11-malloc:
# Test of malloc failure on insert_head
--- trace-11-malloc 6/6
+++ TESTING trace trace-12-malloc:
# Test of malloc failure on insert_tail
--- trace-12-malloc 6/6
+++ TESTING trace trace-13-malloc:
# Test of malloc failure on new
--- trace-13-malloc 6/6
+++ TESTING trace trace-14-perf:
# Test performance of insert_tail, reverse, and sort
--- trace-14-perf 6/6
+++ TESTING trace trace-15-perf:
# Test performance of sort with random and descending orders
# 10000: all correct sorting algorithms are expected pass
# 50000: sorting algorithms with O(n^2) time complexity are expected failed
# 100000: sorting algorithms with O(nlogn) time complexity are expected pass
--- trace-15-perf 6/6
+++ TESTING trace trace-16-perf:
# Test performance of insert_tail
--- trace-16-perf 6/6
+++ TESTING trace trace-17-complexity:
# Test if time complexity of q_insert_tail, q_insert_head, q_remove_tail, and q_remove_head is constant
Probably constant time
Probably constant time
Probably constant time
Probably constant time
--- trace-17-complexity 5/5
--- TOTAL 100/100
```
### q_swap
Didn't open the leetcode link at first instance so implemented the first version swapping the values and not the nodes.
```c=
void q_swap(struct list_head *head)
{
if (!head) {
return;
}
struct list_head *curr, *after;
curr = head->next;
while (curr != head && curr->next != head) {
after = curr->next;
curr->next = after->next;
curr->next->prev = curr;
after->prev = curr->prev;
after->prev->next = after;
after->next = curr;
curr->prev = after;
curr = curr->next;
}
}
```
Here I will try to explain how this code works. We should consider carefully the relationship between the nodes in play.
```graphviz
digraph g{
1 -> 2;
2 -> 1;
2 -> 3;
3 -> 2;
3 -> 4;
4 -> 3;
}
```
The logic is to first work with either the links of <b>1 and 3</b> or <b> 2 and 4</b> and last with the links of <b>2 and 3</b> (Because we don't want to mess up with the links that are the most obvious to us in the first instance)
### q_reverse
Use list_for_each_safe to iterate over the nodes and reversing its link. ( Should use _safe version because during the process we will be modifiying the node's link)
After reversing all the node's link we shouldn't forget to reverse the head's link also.
```c=
void q_reverse(struct list_head *head)
{
if (!head || head == head->next) {
return;
}
// reverse the queue excluding head
struct list_head *node, *safe, *temp;
list_for_each_safe (node, safe, head) {
temp = node->next;
node->next = node->prev;
node->prev = temp;
}
// reverse head
temp = head->next;
head->next = head->prev;
head->prev = temp;
}
```
### q_sort
Recursive version of mergesort.
For easier implementation we break the circular linked list, so we can just implement a linked list merge sort.
After the list is sorted we have to adjust the prev pointer and reconnect the last node back to the head.
```c=
struct list_head *mergeTwoLists(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 *L1_node = container_of(L1, element_t, list);
element_t *L2_node = container_of(L2, element_t, list);
node = (strcmp(L1_node->value, L2_node->value) <= 0) ? &L1 : &L2;
*ptr = *node;
ptr = &(*ptr)->next;
}
*ptr = (struct list_head *) ((uintptr_t) L1 | (uintptr_t) L2);
return head;
}
struct list_head *mergesort_list(struct list_head *head)
{
if (!head->next)
return head;
// break the list node in the middle
struct list_head *slow = head;
for (struct list_head *fast = head->next; fast && fast->next;
fast = fast->next->next)
slow = slow->next;
struct list_head *mid = slow->next;
slow->next = NULL;
// recursive call
struct list_head *left = mergesort_list(head), *right = mergesort_list(mid);
// merge list
return mergeTwoLists(left, right);
}
void q_sort(struct list_head *head)
{
if (!head || list_empty(head))
return;
// break the circular link list into a regular link list
head->prev->next = NULL;
//sort
head->next = mergesort_list(head->next);
// Adjust the previous pointer to the correct node
struct list_head *ptr = head;
for (; ptr->next; ptr = ptr->next)
ptr->next->prev = ptr;
ptr->next = head;
head->prev = ptr;
}
```
### shuffle
Implement Fisher-Yates Shuffle
```c=
void q_shuffle(struct list_head *head)
{
if (!head || list_empty(head)) {
return;
}
int q_length = q_size(head);
if (q_length == 1) {
return;
}
struct list_head *node, *target_node;
for (node = head->next; node->next != head; node = node->next) {
int target = 0;
while (target == 0) {
target = rand() % q_length;
}
target_node = node;
for (int i = 0; i < target; i++) {
target_node = target_node->next;
}
element_t *node_el = container_of(node, element_t, list);
element_t *target_node_el = container_of(target_node, element_t, list);
// swap values
char *temp = node_el->value;
node_el->value = target_node_el->value;
target_node_el->value = temp;
q_length--;
}
}
```
```c=
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 shuffle 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();
}
```
### References:
Lecture notes:
> [你所不知道的C語言:指標篇](https://hackmd.io/@sysprog/c-pointer)
> [你所不知道的C語言:linked list 和非連續記憶體](https://hackmd.io/@sysprog/c-linked-list)
Classmate's work :
> [kevinshieh0225](https://hackmd.io/@Masamaloka/2022q1-hw1)
> [happyjack91630](https://hackmd.io/@Z80E9t1oQmejDKgmGy_dBQ/BkTGEADgq)
> [jimmy-liu1021](https://hackmd.io/WuvH2ILMSaat53sYeXmivA?both)