کار کردن با دادههای دودویی مفهومی مهم در هر زبانی است و Elixir روشی شیک برای نوشتن، تطبیق دادن و ساختن دادههای دودویی در اختیار شما میگذارد.
در Elixir به دادههای دودویی، نوع رشتهی بیتی میگویند. نوع دادهی دودویی (که نباید با دادههای دودویی به معنای کلی اشتباه گرفته شود) شکل خاصی از رشتهی بیتی است که در تمرینی دیگر دربارهی آن صحبت میکنیم.
لیترالهای رشتهی بیتی با استفاده از شکل ویژهی <<>> تعریف میشوند. وقتی یک لیترال رشتهی بیتی تعریف میکنید، آن را در قالب قطعههایی تعریف میکنید. هر قطعه یک مقدار و یک نوع دارد که با عملگر :: از هم جدا میشوند. نوع مشخص میکند که برای کدگذاری مقدار، چند بیت استفاده میشود. میتوان نوع را کاملاً حذف کرد که در این صورت بهطور پیشفرض یک مقدار صحیح ۸ بیتی خواهد بود.
# This defines a bitstring with three segments of a single bit each
<<0::1, 1::1, 0::1>>
نوشتن نوع به شکل ::1 شکل کوتاهشدهی ::size(1) است. اگر اندازهی بیتها از یک متغیر بیاید، باید از شکل بلندتر استفاده کنید.
وقتی لیترالهای صحیح دودویی را مینویسیم، میتوانیم آنها را مستقیماً در مبنای ۲ بنویسیم و پیشوند 0b را به لیترال اضافه کنیم. توجه کنید که به هر حال هنگام نمایش در نتایج تستها یا در 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) در قالبی متفاوت از قالبی که برای ساختنشان استفاده شده چاپ شوند. این موضوع هنگام یادگیری رشتههای بیتی اغلب باعث سردرگمی میشود.
بهطور پیشفرض، رشتههای بیتی در تکههایی ۸ بیتی (یک بایت) نمایش داده میشوند، حتی اگر آنها را با تکههایی با اندازهی متفاوت ساخته باشید.
<<2011::11>>
# => <<251, 3::size(3)>>
اگر رشتهی بیتیای بسازید که نمایندهی یک رشتهی قابل چاپ کدگذاریشده با UTF-8 باشد، بهصورت یک رشته نمایش داده میشود.
<<>>
# => ""
<<65, 66, 67>>
# => "ABC"
نقاشی با شماره (که با اسم رنگآمیزی با شماره هم شناخته میشود) تصاویری سیاهوسفید برای رنگآمیزی است. روی بخشهای مختلف تصویر شمارههای متفاوتی نوشته میشود و این شمارهها به رنگهای مشخصی در یک پالت رنگ از پیش تعریفشده اشاره میکنند. هدف این است که بخشها را با رنگهای درست پر کنید تا در پایان تصویری زیبا و رنگارنگ نمایان شود. این یک فعالیت آرامبخش برای کودکان و بزرگسالان است.
قرار است یک برنامهی نقاشی با شماره به زبان Elixir بنویسید. میخواهید برنامهتان بتواند تصاویر را در یک قالب دادهای سفارشی وارد و صادر کند. تصمیم گرفتهاید دادههای تصویرتان را در فایلهای دودویی ذخیره کنید.
این تمرین فرض میکند که با اعداد دودویی آشنا هستید و اصول تبدیل اعداد دودویی به اعداد دهدهی و اعداد دهدهی به اعداد دودویی را میدانید.
تصور کنید تصویری از یک شکلک لبخند دارید، مثل تصویری که در ادامه میبینید. پسزمینهی تصویر سفید است. شکلک یک حاشیهی سیاه و یک رنگ پرکنندهی زرد دارد.
این تصویر از ۳ رنگ استفاده میکند. فرض کنید به این رنگها اندیس نسبت میدهیم:
0 (دودویی: 0b00) برای سفید،1 (دودویی: 0b01) برای سیاه،2 (دودویی: 0b10) برای زرد.حالا میتوانیم با این اندیسهای رنگ، رنگ هر پیکسل را نمایش دهیم.
| شکلک | شکلک با اندیسهای رنگ |
|---|---|
![]() |
![]() |
تابع PaintByNumber.palette_bit_size/1 را پیادهسازی کنید. این تابع باید تعداد رنگهای پالت را بگیرد و برگرداند که برای نمایش این تعداد اندیس رنگ بهصورت اعداد دودویی، چند بیت لازم است. اندیسهای رنگ همیشه از ۰ شروع میشوند و اعداد صحیحی صعودی و پیوستهاند.
برای مثال، نمایش ۱۳ رنگ متفاوت به ۴ بیت نیاز دارد. ۴ بیت میتواند تا ۱۶ اندیس رنگ (۲ به توان ۴) را ذخیره کند. ۳ بیت کافی نیست، چون ۳ بیت فقط میتواند تا ۸ اندیس رنگ (۲ به توان ۳) را ذخیره کند.
PaintByNumber.palette_bit_size(13)
# => 4
توجه: در کتابخانهی استاندارد Elixir تابع log2 وجود ندارد. بعداً یاد میگیرید که چطور در Elixir از کتابخانههای Erlang استفاده کنید، جایی که میتوانید این تابع را پیدا کنید. فعلاً این کار را بهجای آن، با بازگشت و عملگر توان ** حل کنید.
تابع PaintByNumber.empty_picture/0 را پیادهسازی کنید. این تابع باید یک رشتهی بیتی خالی برگرداند.
برای آزمون دستی قابلیتهای پایهی برنامهتان از یک تصویر آزمایشی از پیش تعریفشده استفاده میشود. تصویر آزمایشی از ۴ پیکسل با ۴ رنگ متفاوت تشکیل شده است.
تابع PaintByNumber.test_picture/0 را پیادهسازی کنید. این تابع باید رشتهی بیتیای برگرداند که از ۴ بخش تشکیل شده است. اندازهی بیت هر بخش باید ۲ باشد. مقدار بخشها باید ۰، ۱، ۲ و ۳ باشد.
تابع 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>>)