Elixir でビット操作を行うコードを書いたんですが、使うべき関数やガードで混乱しそうになったので、明日の自分のためにまとめてみました。
ガード
# | 入力 | is_binary/1 |
is_bitstring/1 |
---|---|---|---|
1 | "あいう" |
true |
true |
2 | <<12, 34>> |
true |
true |
3 | <<12::5, 34::5>> |
false |
true |
is_binary/1
はバイト列(ビット長が 8 の倍数になっているビット列)の場合に true になります。
上記の 3 番目は 10 ビットのビット列なので false を返します。
全体で 8 の倍数であれば true になりますので、次のように書いても true になります。
iex> is_binary(<<12::3, 34::5>>) true
パタンマッチング
パタンマッチングでも binary
で受ける場合には、そのビット長が 8 の倍数になっている必要があります。
端数があるとエラーになります。
iex> <<a, rest::binary>> = "あ" "あ" iex> a 227 iex> rest <<129, 130>>
iex> <<a, rest::binary>> = <<123, 234>> <<123, 234>> iex> a 123 iex> rest <<234>>
iex> <<a, rest::binary>> = <<123::5, 234::5>> ** (MatchError) no match of right hand side value: <<218, 2::size(2)>>
任意の長さのビット列を受けるには bitstring
を指定します。
iex> <<a, rest::bitstring>> = "あ" "あ" iex> a 227 iex> rest <<129, 130>>
iex> <<a, rest::bitstring>> = <<123, 234>> <<123, 234>> iex(79)> a 123 iex(80)> rest <<234>>
iex(81)> <<a, rest::bitstring>> = <<123::5, 234::5>> <<218, 2::size(2)>> iex(82)> a 218 iex(83)> rest <<2::size(2)>>
長さとサイズ
# | 入力\関数 | String.length/1 |
byte_size/1 |
bit_size/1 |
---|---|---|---|---|
1 | "あいう" |
3 | 9 | 72 |
2 | <<123, 234>> |
2 | 2 | 16 |
3 | <<12::5, 34::5>> |
エラー*1 | 2 | 10 |
*1 ** (FunctionClauseError) no function clause matching in String.Unicode.length/1
String.lenth/1
は UTF-8 としての文字数を返します。
ビット列の長さが 8 の倍数でない場合、エラーが発生します。
byte_size/1
はバイト数を返しますが、興味深いのはビット列の長さが 8 の倍数でないばあいでもエラーにはならず、8 で割って切り上げた数字を返す点です。
ちなみに length
と size
という名前について。
実行が線形時間のものには length
を、定数時間のものには size
をつけるようにしてるとのこと。
また、byte_size/1
と bit_size/1
はガードに記述できるためか、ドキュメントでは Functions でなく Guards に記載されています。
型
# | 型 | 内容 |
---|---|---|
1 | binary() |
バイト列(長さが 8 の倍数のビット列) |
2 | bitstring() |
ビット列 |
3 | String.t() |
文字列(binary() のエイリアス) |
4 | string() |
Erlang の文字列(文字リスト charlist() に同じ) |
"string" という単語が 3 回も出てくるので、うろ覚えでいると足をすくわれそうです。
String.t()
は binary()
と同じものをさしていますが、値が文字列(UTF-8 エンコーディングのバイナリ)であることをドキュメントで明確にしたいばあいのために用意されているようです。
また string()
は Erlang の文字列型であるため、Elixir 内では charlist()
を使うように勧められています。
やりたかったこと
「任意のビット列を任意のサイズで分割し、左詰めでバイト列にしたリストを返す」という機能を実現すべく四苦八苦していました。
defmodule Bin do @spec chunk_every(bitstring(), pos_integer()) :: [binary()] def chunk_every(bin, n) when is_bitstring(bin) and is_integer(n) and n > 0 do padding_size = rem(8 - rem(n, 8), 8) bin |> Stream.unfold(fn <<>> -> nil <<chunk::size(n), rest::bitstring>> -> {<<chunk::size(n), 0::size(padding_size)>>, rest} chunk -> chunk_size = bit_size(chunk) padding_size = n - chunk_size + padding_size {<<chunk::bitstring, 0::size(padding_size)>>, <<>>} end) |> Enum.to_list() end end
たとえば。 60 ビットのビット列を 12 ビットごとに分割し、分割したそれぞれのビットを左詰めしたバイト列のリストとして取得したいばあい。
このばあい、12 ビットのビット列は 16 ビット = 2 バイトのバイト列に収まるので、2 バイトのバイト列のリストが返ります。
iex> Bin.chunk_every(<<0x123456789abcdef::60>>, 12) [<<18, 48>>, "E`", <<120, 144>>, <<171, 192>>, <<222, 240>>]
結果がわかりにくいので、パタンマッチングで確認。
iex> Bin.chunk_every(<<0x123456789abcdef::60>>, 12) == [<<0x12, 0x30>>, <<0x45, 0x60>>, <<0x78, 0x90>>, <<0xab, 0xc0>>, <<0xde, 0xf0>>] true
いつか読むはずっと読まない: ≥ 1.6
第 2 版の邦訳が出版されていることを、つい最近になって知りました。 不覚。 買わねば。
- 作者:Thomas,Dave
- 発売日: 2020/12/01
- メディア: 単行本