處理二進位資料在任何語言中都是重要的概念,而 Elixir 提供了優雅的語法來撰寫、配對與建構二進位資料。
在 Elixir 中,二進位資料被稱為位元字串型別。二進位資料_型別_(不要與一般所說的二進位資料混為一談)是位元字串的一種特定形式,我們會在之後的練習中討論。
位元字串字面值使用位元字串特殊形式 <<>> 來定義。定義位元字串字面值時,是以區段為單位來定義。每個區段都有一個值與型別,以 :: 運算子分隔。型別會指定用多少位元來編碼這個值。型別可以完全省略,此時預設為 8 位元的整數值。
# This defines a bitstring with three segments of a single bit each
<<0::1, 1::1, 0::1>>
將型別指定為 ::1 是撰寫 ::size(1) 的簡寫。如果位元大小來自變數,你就必須使用較長的語法。
撰寫二進位整數字面值時,我們可以在字面值前面加上 0b,直接以 2 為基底來表示。請注意,無論是在測試結果中印出,或是在 iex 中使用,它們都會以十進位數字顯示。
<<0b1011::4>> == <<11::4>>
# => true
如果區段的值超出該區段型別的容量,就會從左側被截斷。
<<0b1011::3>> == <<0b0011::3>>
# => true
你可以使用特殊形式在現有的位元字串前方加入資料,也可以在後方附加資料。如果現有的位元字串大小未知,就必須在它上面使用 ::bitstring 型別。
value = <<0b110::3, 0b001::3>>
new_value = <<0b011::3, value::bitstring, 0b000::3>>
# => <<120, 8::size(4)>>
我們可以使用特殊形式來串接儲存在變數中的位元字串。串接兩個大小未知的位元字串時,必須使用 ::bitstring 型別。
first = <<0b110::3>>
second = <<0b001::3>>
concatenated = <<first::bitstring, second::bitstring>>
# => <<49::size(6)>>
模式配對也可以用來從特殊形式中取得值。你必須知道每個想捕捉的片段的位元數,只有一個例外:::bitstring 型別可以用來對大小未知的位元字串進行模式配對,但這只能用在最後一個片段。
<<value::4, rest::bitstring>> = <<0b01101001::8>>
value == 0b0110
# => true
位元字串(由測試執行器或在 iex 中)印出時的格式,可能與用來建立它們的格式不同。這在學習位元字串時常常造成困惑。
預設情況下,位元字串會以 8 位元(一個位元組)的片段顯示,即使你建立它們時使用的片段大小不同也一樣。
<<2011::11>>
# => <<251, 3::size(3)>>
如果你建立的位元字串代表可列印的 UTF-8 編碼字串,它就會以字串的形式顯示。
<<>>
# => ""
<<65, 66, 67>>
# => "ABC"
數字塗色(也稱為「按數字上色」)是一種用來著色的黑白圖片。 圖片上的不同區域會標上不同的數字, 這些數字對應到預先定義好的調色盤中的特定顏色。 目標是用正確的顏色填滿這些區域, 最後呈現出一幅美麗的彩色圖畫。 這是一項大人小孩都能放鬆享受的活動喔。
你的任務是用 Elixir 寫一個數字塗色應用程式。 你希望你的應用程式能夠以自訂的資料格式匯入和匯出圖片。 你決定使用二進位檔案來儲存圖片資料。
這個練習假設你已經熟悉二進位數字, 並且了解二進位數字轉換成十進位數字、以及十進位數字轉換成二進位數字背後的原理。
想像一下,你有一張笑臉的圖片,就像下面這張。 這張圖片有白色的背景。笑臉有黑色的外框和黃色的填充顏色。
這張圖片使用了 3 種顏色。 假設我們為這些顏色指派索引:
0(二進位:0b00)代表白色,1(二進位:0b01)代表黑色,2(二進位:0b10)代表黃色。現在我們可以用這些顏色索引來表示每個像素的顏色。
| 笑臉 | 標上顏色索引的笑臉 |
|---|---|
![]() |
![]() |
實作PaintByNumber.palette_bit_size/1函式。它應該接收調色盤中的顏色數量,並回傳以二進位數字表示這麼多顏色索引所需的位元數。顏色索引一律從 0 開始,並且是連續遞增的整數。
例如,表示 13 種不同的顏色需要 4 個位元。4 個位元最多可以儲存 16 個顏色索引(2^4)。3 個位元會不夠,因為 3 個位元最多只能儲存 8 個顏色索引(2^3)。
PaintByNumber.palette_bit_size(13)
# => 4
注意:Elixir 標準函式庫中沒有log2函式。你之後會學到如何在 Elixir 中使用 Erlang 函式庫,在那裡可以找到這個函式。現在,請改用遞迴和次方運算子**來解決這個任務。
實作PaintByNumber.empty_picture/0函式。它應該回傳一個空的位元字串。
我們會用一張預先定義好的測試圖片,來手動測試你應用程式的基本功能。 這張測試圖片由 4 個像素組成,包含 4 種不同的顏色。
實作PaintByNumber.test_picture/0函式。它應該回傳一個由 4 個區段組成的位元字串。
每個區段的位元大小應該是 2。這些區段的值應該分別是 0、1、2 和 3。
實作PaintByNumber.prepend_pixel/3函式。它應該接收三個引數:要加上像素的圖片位元字串、調色盤中的顏色數量,以及新像素顏色的索引。它應該回傳一個位元字串,其中的圖片在開頭加上了新的像素。
picture = <<2::4, 0::4>>
color_count = 13
pixel_color_index = 11
PaintByNumber.prepend_pixel(picture, color_count, pixel_color_index)
# => <<178, 0::size(4)>>
# (which is equal to <<11::4, 2::4, 0::4>>)
實作PaintByNumber.get_first_pixel/2函式。它應該接收兩個引數:要讀取的圖片位元字串,以及調色盤中的顏色數量。它應該回傳指定圖片中第一個像素的顏色索引。當傳入空圖片時,它應該回傳nil。
picture = <<19::5, 2::5, 18::5>>
color_count = 20
PaintByNumber.get_first_pixel(picture, color_count)
# => 19
實作PaintByNumber.drop_first_pixel/2函式。它應該接收兩個引數:要移除像素的圖片位元字串,以及調色盤中的顏色數量。它應該回傳移除第一個像素後的圖片。當傳入空圖片時,它應該回傳空圖片。
picture = <<2::3, 5::3, 5::3, 0::3>>
color_count = 6
PaintByNumber.drop_first_pixel(picture, color_count)
# => <<180, 0::size(1)>>
# (which is equal to <<5::3, 5::3, 0::3>>)
實作PaintByNumber.concat_pictures/2函式。它應該接收兩個引數,也就是兩個位元字串。它應該回傳把第一個引數加到第二個引數開頭後所得的位元字串。
picture1 = <<52::6, 51::6>>
picture2 = <<0::6, 34::6, 12::6>>
PaintByNumber.concat_pictures(picture1, picture2)
# => <<211, 48, 34, 12::size(6)>>
# (which is equal to <<52::6, 51::6, 0::6, 34::6, 12::6>>)