Powered by Drupal, an open source content management system

โฆษณาโดย Google

binary

Erlang binary pattern matching

ในโปรแกรมที่ผมกำลังทำอยู่ตอนนี้ จำเป็นต้องอ่านเท็กไฟล์ขนาดใหญ่เข้ามา แล้วเอามาแบ่งออกเป็นส่วนย่อยๆก็คือเอามา split นะแหละครับ แล้วทีนี้เวลาอ่านข้อมูลเอามาจากไฟล์ใน erlang จะใช้ฟังก์ชัน read_file จาก module file โดยผลลัพธ์ที่ได้จะอยู่ในรูปแบบประมาณนี้

{ok, <<"attaccggttaaccttgg">>}

คือได้ tuple ตัวแรกเป็น ok คืออ่านได้ปกติ ตัวหลังคือข้อมูลที่เป็นแบบ binary ใน erlang ข้อมูลแบบ binary จะอยู่ในสัญลักษณ์ <<>>

ตอนแรกทำโปรแกรมให้อ่านค่าจากไฟล์แล้วให้โปรแกรมส่งแค่ผลลัพธ์ข้างใน binary ออกมาเป็น string แล้วเอา string ไปใช้งานต่อ ผลที่ได้คือตอนทำการแบ่งสตริงออกเป็นลิสต์ของสตริงย่อยๆ ทำงานช้ามาก ขนาดที่ทดลองคือ ล้านกว่าตัวอักษร

ก็ลองค้นหาดูว่า มีวิธียังไงบ้างให้สามารถอ่านข้อมูลขนาดใหญ่เข้ามาจัดการได้อย่างรวดเร็ว ก็ไปเจอว่าส่วนใหญ่แล้วเขาจะไม่แปลงข้อมูลจาก binary ไปเป็น string แต่จะจัดการกับ binary เลย เช่นตรงนี้ก็คือ แบ่งตัว binary ที่ได้จากการอ่านไฟล์เลย โดยอาศัยการใช้ pattern matching กับข้อมูลแบบ binary เพื่อทำการ bind ค่าจาก binary ออกมาเป็น binary ย่อยๆตามจำนวนที่ต้องการ

ข้อมูลแบบ binary จะอยู่ใน pattern แบบนี้

<<"abc">>

ตัวอย่างคือมีข้อมูลอยู่ 3 bytes (24 bits แต่ละตัวอักษรใช้ 1 byte ใน 1 byte มี 8 bits)

ถ้าเราต้องการ binding ค่าของ a ออกมาจะทำได้แบบนี้

<<A,Rest/binary>> = <<"abc">>.

คือตรงส่วน pattern เราก็ทำให้อยู่ในฟอร์มของ binary มี <<>> ครอบเหมือนกัน ผลที่ได้คือ A จะถูก bound ค่าของ 8 bits แรกใน binary <<"abc">> ถ้าเราสั่ง

A.
97

จะเห็นว่าจะได้ค่า 97 ก็คือค่ารหัส ASCII ของอักษร a นั่นเอง ส่วน Rest/binary เป็นการบอกว่า Rest คือส่วนที่เหลือทั้งหมด และส่วนที่เหลือทั้งหมดนี้เป็น binary ทั้งก้อน ผลที่ได้คือ Rest จะเท่ากับ <<"bc">> นั่นเอง

Rest.
<<"bc">>

ถ้าเราต้องการนำ A กลับไปเป็น binary อีกครับก็สามารถทำได้โดยใช้ <<>> ครอบ A

<<A>>.
<<"a">>

นอกจากนี้ เราสามารถกำหนดได้ว่าเราจะทำการ matching ค่าจาก binary เป็นจำนวนกี่บิต จะเห็นว่าปกติเป็น 8 บิต ตัวอย่างเช่น เราต้องการดึงค่าจาก <<"abc">> ออกมา 2 ตัวหน้า คือ "ab" จะเห็นว่าตัวอักษรมีทั้งหมด 2 ตัวแสดงว่าใช้ทั้งหมด 16 bits เราสามารถ match ได้ด้วยวิธีแบบนี้

<<A:16, Rest/binary>> = <<"abc">>.

ผลที่ได้คือ Rest จะเหลือ

<<"c">>

ส่วน A จะได้ค่าเป็น

24930

คือเป็นค่าที่เกิดจาก bit ของ $a และ $b มาต่อกัน
ถ้าเราต้องการเอา A ไปสร้างเป็น binary ที่มี "ab" ทำได้โดยเอา A ไปครอบด้วย <<>> เหมือนเดิม แต่เราต้องใส่รายละเอียดจำนวนบิตเหมือนเดิมกับตอนที่เอา binding ค่าออกมาด้วยเช่น

<<A:16>>.
<<"ab">>

ตัวอย่างที่ผมเอาไปใช้ใน my_split ฟังก์ชันที่แบ่ง binary แต่ผมจะแบ่งแบบคาบเกี่ยวกัน เช่น แบ่ง 2000 แต่หักออก 1000 แล้วแบ่งอีก 2000

-module(my_binary).
-export([my_split/2]).

my_split(BinList,N) -> my_split(BinList,N,[]).

my_split(<<>>,_,R) -> R;
my_split(BinList,N,R) -> Length = N*8,
                         BinListLength = bit_size(BinList),
                         CheckLength = Length =< BinListLength,

                         if CheckLength == true ->
                                        <<Take:Length,_/binary>> = BinList,
                                        DropLength = Length div 2,
                                        <<_:DropLength,DropRest/binary>> = BinList,
                                        my_split(DropRest,N,[<<Take:Length>>|R]);
                                   true ->
                                        <<Take:BinListLength,Rest/binary>> = BinList,
                                        my_split(Rest,N,[<<Take:BinListLength>>|R])
                         end.