contributed by <gg21-aping
>
MikazkiHikari
請確認是否有按照預期進行rebase;然後 queue.[ch]的部分寫的很詳盡,但其他的項目呢?
element_t
: the element to be linked in the queue, carrying a pointer to an array holding string
.queue_contex_t
: can be considered as the config of the queue structure, carrying the pointer to the head of the queue, the size, and the id.queue.[ch]
queue_new()
參考 Linux Kernel 實作佇列,使用 INIT_LIST_HEAD()
, 將 list_head
的 next
和 prev
指標都指向結構本身。
q_free()
考量僅是將節點自佇列中移除,但不釋放其記憶體空間,可透過 list_for_each_entry_safe()
保存下個節點的指標,並逐一走訪佇列中各節點。
提交程式碼的時候遇到一些問題:
list_for_each_entry_safe()
都沒有在函式名稱及 (
間插入一個空白。
感謝社團同學提出參考 Linux 核心程式碼對 .clang-format
的操作,新增 SpaceBeforeParens
並提出 PR。cppcheck
觸發的 unusedlabel
警告。參考 issue#211 的討論,只能將 cppcheck
從 ubuntu 24.04 LTS 的官方版本 2.13 再往上更新。Commit e0c6b11 解決了 unusedlabel
警告,但依然會觸發 uninitvar
警告。因此提了 issue#229 的討論。
q_insert_head/tail()
使用 strdup()
複製字串 s
,避免原始的 s
指向的記憶體區塊被釋放或更動後影響到 e->value
。
The
strdup()
function returns a pointer to a new string which is a
duplicate of the string s. Memory for the new string is obtained
withmalloc(3)
, and can be freed withfree(3)
.
strdup()
透過 malloc(strlen(s) + 1)
來動態分配記憶體空間,再透過 strcopy()
將原始字串的內容複製到新分配的記憶體。
q_remove_head/tail()
透過 list_entry()
系列操作可取得想要的節點資訊。注意 strncpy(*dest, *src, n)
在 destination 長度比 source 長度長的時候,會將剩餘空白的位置都自動補上終止符 \0
,反之則 dest
指向的字串將不會有終止符。因此,為了避免呼叫者使用可能未正確終止的字串而導致的各種問題,做了一些防禦機制:
unlink
被認定是 american english,但unlinked
或unlinks
均不被aspell
認定為 american english,Functions
也不被認定為 american english QQ
q_size()
原本想透過 container_of()
直接使用 queue_contx_t
中的成員 size
,但觀察 qtest.c
中對 q_size()
的呼叫,發現傳入的值有時是成員 q
,有時是成員 chain
。因此貿然的直接定位會無法取得正確的記憶體位置。因此仍是透過逐一走訪的方式計算。
q_delete_mid()
鏈結的中間節點可以透過兩次走訪鏈結取得,但透過 Hare and Tortoise Algorithm(快慢指標),我們可以只走訪鏈結一次,便快速定位到中間節點。僅需特別注意的是,此作業提供的結構為 circular doubly linked list,亦即指標不會指向 NULL
,且單一節點便能取得其連結的前後節點。
注意 list_del()
並不會釋放記憶體空間,僅是將該節點從鏈結中移除。節點仍會存在,但其指標指向的位址不能被保證;若要再次將節點加入鏈結中,依然要先呼叫 LIST_INIT_HEAD()
。
q_delete_dup()
在 queue.h
的註解中,並沒有提到所有呼叫此函式的鏈結都是有序的,但卻附上 leetcode 刪除有序鏈結中的重複節點。所以一開始會有點不太確定課程期待的寫法是什麼。若能保證是有序鏈結,那麼透過雙指標,就能有效率的完成刪除重複節點的行為。但若無法保證傳入的鏈結為有序,就需要透過例如雜湊表的方式,來達到最佳的效能。
參考數位同學的解法,推估該函式就是預設傳入的節點都為有序,因此先透過雙指標的方式完成此函式。逐一走訪佇列中各項元素,並透過 strcmp
做 char
的比較。
其中 strcmp()
規定傳入的字串必須是 null-ended strings(即字串必須是 \0
結尾),雖然在 queue.h
中並未明定,但若節點的 value
成員不存在,設定回傳 false
。
後來發現我把題目理解成 A -> B -> B -> C
要變成 A -> B -> C
的意思,但其實應該要把 B
完全移除。所以又做了以下修正。透過變數 should_delete
紀錄最後一個重複節點,並進行刪除。
q_swap()
透過兩個指標,並使用 list_move()
可以快速的完成兩兩一組的交換操作。此時,curr
指向一組節點中的第一個節點,而 next
指向下一組節點中的第一個節點。
更新:可透過呼叫 q_reverseK(head, 2)
來取代原本的 swap 操作。
q_reverse()
透過逐一走訪各節點並呼叫 list_move()
將各節點移至 head
節點後,即可完成整串鏈結的反轉。
q_reverseK()
透過三個指標 start
、end
和 iter
來做鏈結的部分反轉。
有特別注意變數宣告的位置,原本將 end
和 iter
一起宣告在整個函式區塊的最頂部 (beginning of the block);但考慮該二變數僅在迴圈中使用,應定義在最小作用域的程式碼區塊中的最頂部,因此做了修正。
q_sort()
考量到資料結構為鏈結串列,使用 merge sort 的方式對鏈結進行排序。概念如下:
list_cut_position()
進行切分。q_merge_two()
進行排序。is_descend()
函式來判斷排序順序。q_ascend/descend()
題目要求將所有破壞升序、降序法則的節點刪除,即在升序的鏈結串列中,較小的元素不能在後面出現,降序則反。若從鏈結的起點開始往後走,判斷上會非常困難。但若從尾部開始往前走,則僅需紀錄一個 extreme
值,在升序排列中,遇到比 extreme
大的值一律刪除,在降序排列中則反。
因此我首先寫了 list_for_each_entry_safe_reverse
的 macro 來協助我完成安全的迴圈操作。
並創建一個 q_make_monotonic()
的函式,透過對變數 descend
的控制,即可讓 q_ascend()
和 q_descend()
同時呼叫。
q_merge()
藉由之前完成 q_sort()
開發過的 q_merge_two()
,用暴力解的方式,兩兩合併在一起。