環狀緩衝區、循環緩衝區或環形緩衝區是一種資料結構,它使用單一固定大小的緩衝區,就好像頭尾相連一樣。
環狀緩衝區一開始是空的,並有某個預先定義好的長度。 例如,這是一個有 7 個元素的緩衝區:
[ ][ ][ ][ ][ ][ ][ ]
假設把 1 寫入緩衝區的中間(在環狀緩衝區裡,實際的起始位置並不重要):
[ ][ ][ ][1][ ][ ][ ]
接著假設再加入兩個元素,也就是 2 和 3,它們會被附加在 1 之後:
[ ][ ][ ][1][2][3][ ]
如果接著從緩衝區移除兩個元素,被移除的會是緩衝區裡最舊的值。 在這個例子裡,被移除的兩個元素是 1 和 2,緩衝區裡只剩下一個 3:
[ ][ ][ ][ ][ ][3][ ]
如果緩衝區裡有 7 個元素,那它就完全滿了:
[5][6][7][8][9][3][4]
當緩衝區滿了,就會引發錯誤,提醒用戶端在有空位之前,後續的寫入都會被阻擋。
當緩衝區滿了,用戶端可以選擇用強制寫入來覆寫最舊的資料。 在這個例子裡,會再加入兩個元素,也就是 A 和 B,它們會覆寫掉 3 和 4:
[5][6][7][8][9][A][B]
3 和 4 已被 A 和 B 取代,於是 5 現在成了緩衝區裡最舊的資料。 最後,如果移除兩個元素,回傳的會是 5 和 6,於是緩衝區變成:
[ ][ ][7][8][9][A][B]
因為還有可用的空間,如果用戶端再次使用覆寫來存放 C 和 D,那麼會使用先前存放 5 和 6 的空間,而不是 7 和 8 的位置。 7 仍然是最舊的元素,而緩衝區又再度滿了。
[C][D][7][8][9][A][B]
定義一個參數化的複合型別CircularBuffer{T},用來存放型別為T的元素,並撰寫一個建構子
CircularBuffer{T}(capacity::Integer) where {T} -> CircularBuffer{T}
建立一個最多可存放capacity個元素的實例。
擴充Base中的下列函式,讓它們能作用於CircularBuffer:
Base.push!(cb::CircularBuffer, item; overwrite::Bool=false):將元素item插入cb的尾端,然後回傳cb。如果cb已經滿了,當overwrite為false(預設值)時,就拋出BoundsError;否則,當overwrite為true時,移除第一個元素,以騰出空間給item。Base.popfirst!(cb::CircularBuffer):移除並回傳cb的第一個元素。Base.empty!(cb::CircularBuffer):移除cb中的所有元素,然後回傳空的cb。這個練習相當龐大,也可能很複雜,這讓引導變得更有挑戰性,也更花時間。為了幫你的導師省點力,請等到導師審閱過你練習第一部分的解法之後,再提交加分練習的程式碼。
擴充你的CircularBuffer,讓它能通過 DataStructures.jl套件中CircularBuffer的測試。這些測試已包含在這個 Exercism 練習所提供的測試中,但預設為停用;若要啟用這些測試,請在檔案或筆記本中加入頂層的那一行enable_bonus_tests = true。
若要通過這些測試,你需要將CircularBuffer宣告為AbstractVector的子型別,並定義兩個函式:
capacity(cb::CircularBuffer):回傳cb的容量。isfull(cb::CircularBuffer):若cb已滿則回傳true。接著,你必須確保Base中下列函式能正確地搭配CircularBuffer運作:append!、empty!、pop!、pushfirst、setindex!、collect、eltype、first、getindex、isempty、iterate、last、length和size。
提示:你不需要,也不應該,擴充所有這些函式!只要將CircularBuffer定義為AbstractVector的子型別,原本為AbstractVector定義的泛型函式就會接受CircularBuffer作為輸入。請參閱 Julia 手冊中關於介面的章節:
Julia 的許多威力與可擴充性,都來自一系列的非正式介面。只要為某個自訂型別擴充幾個特定的方法,該型別的物件不僅能獲得這些功能,還能被用在其他以泛型方式建立於這些行為之上的方法中。
你將需要翻閱 Julia 的Base模組原始碼,查看函式定義,並判斷該擴充哪些函式。若要找出某個函式呼叫對應的相關程式碼,你可以使用@which巨集,找出該函式呼叫所分派到的特定方法。它也會顯示該方法定義所在的檔案與行號(在透過 IJulia 使用的 Jupyter Notebook 中,它甚至會提供一個連到 GitHub 上相關程式碼的連結)。
如果你在 REPL 中工作,或許會偏好使用@edit巨集,在你的預設文字編輯器中開啟相關的檔案與行號。