这个月我一直在学习灵丹妙药,当时我想将二进制对象转换为位列表以进行模式匹配。
我的研究使我here看到了一篇文章,展示了这样做的方法。但是,我不完全理解传递给extract
函数的参数之一。
我可以复制并粘贴代码,但是我想了解这里的内容。
参数是这样的:<<b :: size(1), bits :: bitstring>>
。
我的理解
我了解到<< x >>
表示二进制对象x
。从逻辑上看,这似乎与在List上执行[head | tail] = list
类似,以获取第一个元素,然后剩余的元素作为新列表称为tail。
我不了解的
但是,我不熟悉语法,也从未见过Elixir中的::
,也从未见过以逗号分隔的二进制对象:,
。我还没有看到Elixir中使用过size(x)
,也从未遇到过bitstring
。
底线
如果有人可以确切解释该参数的语法是如何分解的,或者将我引向我非常感激的资源。
为方便起见,该文章中的代码:
defmodule Bits do
# this is the public api which allows you to pass any binary representation
def extract(str) when is_binary(str) do
extract(str, [])
end
# this function does the heavy lifting by matching the input binary to
# a single bit and sends the rest of the bits recursively back to itself
defp extract(<<b :: size(1), bits :: bitstring>>, acc) when is_bitstring(bits) do
extract(bits, [b | acc])
end
# this is the terminal condition when we don't have anything more to extract
defp extract(<<>>, acc), do: acc |> Enum.reverse
end
IO.inspect Bits.extract("!!") # => [0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1]
IO.inspect Bits.extract(<< 99 >>) #=> [0, 1, 1, 0, 0, 0, 1, 1]
答案 0 :(得分:1)
您可以阅读所有这些here的简短答案:
::
与后卫类似,例如a when is_integer(a)
,在您情况下,size(1)
期望使用1位二进制,
是匹配二进制文件之间的分隔符,例如|
中的[x | []]
或[a, b]
中的逗号bitstring
是二进制文件的超集,您可以阅读有关here和here的信息,任何二进制文件都可以表示为位串iex> ?h
104
iex> ?e
101
iex> ?l
108
iex> ?o
111
iex> <<104, 101, 108, 108, 111>>
"hello"
iex> [104, 101, 108, 108, 111]
'hello'
反之亦然
iex> <<1, 2, 3>>
<<1, 2, 3>>
答案 1 :(得分:0)
经过研究,我意识到我忽略了位于elixir-lang上的一些重要信息。
根据文档,<<x :: size(y)>>
表示一个位串,其十进制值为x
,并由一串长度为y
的位表示。
此外,<<binary>>
始终会尝试将值从左到右的方向合并为bytes
或8-bits
,但是,如果位数不能被8整除,则将x个字节,后跟一个位串。
例如:
iex> <<3::size(5), 5::size(6)>> # <<00011, 000101>>
<<24, 5::size(3)>> # automatically shifted to: <<00011000(24) , 101>>
现在,Elixir还允许我们对二进制文件和位串进行模式匹配:
iex> <<3 :: size(2), b :: bitstring>> = <<61 :: size(6)>> # [11] [1101]
iex> b
<<13 :: size(4)>> # [1101]
因此,我完全误解了二进制文件和二进制字符串以及两者之间的模式匹配。
答案 2 :(得分:0)
Elixir模式匹配似乎使人头脑容易使用 结构化的二进制数据。
是的。您可以感谢erlang发明者。
根据文档,
<<x :: size(y)>>
表示一个位串, whos的十进制值为x,由一串字符串表示,即 长度为y。
让我们哑巴一点:<<x :: size(y)>>
是将x插入y位的整数。例子:
<<1 :: size(1)>> => 1
<<1 :: size(2)>> => 01
<<1 :: size(3)>> => 001
<<2 :: size(3)>> => 010
<<2 :: size(4)>> => 0010
binary
类型的位数可以被8整除,因此二进制类型具有整数个字节(1个字节= 8位)。 bitstring
中的位数不能被8整除。这就是binary
类型和bitstring
类型之间的区别。
我知道<< x >>表示二进制对象x。逻辑上对我来说 看起来这类似于执行:[head |尾] =清单 在列表上,以获取第一个元素,然后将其余元素作为 新列表称为tail。
是:
defmodule A do
def show_list([]), do: :ok
def show_list([head|tail]) do
IO.puts head
show_list(tail)
end
def show_binary(<<>>), do: :ok
def show_binary(<<char::binary-size(1), rest::binary>>) do
IO.puts char
show_binary(rest)
end
end
在iex中:
iex(6)> A.show_list(["a", "b", "c"])
a
b
c
:ok
iex(7)> "abc" = <<"abc">> = <<"a", "b", "c">> = <<97, 98, 99>>
"abc"
iex(9)> A.show_binary(<<97, 98, 99>>)
a
b
c
:ok
或者您可以将二进制中的整数解释为普通的旧整数:
def show(<<>>), do: :ok
def show(<<ascii_code::integer-size(8), rest::binary>>) do
IO.puts ascii_code
show(rest)
end
在iex中:
iex(6)> A.show(<<97, 98, 99>>)
97
98
99
:ok
utf8
类型非常有用,因为它将获取所需的字节以获取整个utf8字符:
def show(<<>>), do: :ok
def show(<<char::utf8, rest::binary>>) do
IO.puts char
show(rest)
end
在iex中:
iex(8)> A.show("ۑ")
8364
235
:ok
如您所见,uft8
类型返回字符的Unicode代码点。要将字符作为字符串/二进制文件获取:
def show(<<>>), do: :ok
def show(<<codepoint::utf8, rest::binary>>) do
IO.puts <<codepoint::utf8>>
show(rest)
end
您将代码点(一个整数)用于创建二进制/字符串<<codepoint::utf8>>
。
在iex中:
iex(1)> A.show("ۑ")
€
ë
:ok
但是,您无法为utf8
类型指定大小,因此,如果要读取多个utf8字符,则必须指定多个段。
当然,段rest::binary
(即未指定大小的binary
类型)非常有用。它只能出现在模式的结尾,rest::binary
就像贪婪的正则表达式:(.*)
。 rest::bitstring
也是如此。
尽管elixir文档在任何地方都没有提及,但段中的total number of bits
就是其中之一:
| | |
v v v
<< 1::size(8), 1::size(16), 1::size(1) >>
实际上是unit * size
,其中每种类型都有默认的unit
。段的默认类型为integer
,因此上方每个段的类型默认为integer
。整数的默认unit
为1位,因此第一段中的总位数为:8 * 1 bit = 8 bits
。 unit
类型的默认binary
是8位,因此段如下:
<< char::binary-size(6)>>
的总大小为6 * 8 bits = 48 bits
。等效地,size(6)
只是字节数。您可以像unit
一样指定size
,例如<<1::integer-size(2)-unit(3)>>
。该段的总位大小为:2 * 3 bits = 6 bits
。
但是,我对语法不熟悉
检查一下:
def bitstr2bits(bitstr) do
for <<bit::integer-size(1) <- bitstr>>, do: bit
end
在iex中:
iex(17)> A.bitstr2bits <<1::integer-size(2), 2::integer-size(2)>>
[0, 1, 1, 0]
等效地:
iex(3)> A.bitstr2bits(<<0b01::integer-size(2), 0b10::integer-size(2)>>)
[0, 1, 1, 0]
Elixir倾向于使用库函数来抽象递归,因此通常不必像在链接中那样提出自己的递归定义。但是,该链接显示了一种标准的基本递归技巧:在函数调用中添加 accumulator 以收集希望函数返回的结果。该函数也可以这样写:
def bitstr2bits(<<>>), do: []
def bitstr2bits(<<bit::integer-size(1), rest::bitstring>>) do
[bit | bitstr2bits(rest)]
end
链接上的累加器函数是 tail recursive ,这意味着它占用了恒定(少量)的内存-无论需要多少个递归函数调用来逐步访问该位串。一千万比特的比特串?需要1000万个递归函数调用?那将只需要少量的内存。在过去,我发布的替代定义可能会使您的程序崩溃,因为它会为每个递归函数调用占用越来越多的内存,并且如果位串足够长,则所需的内存量将太大,您将得到 stackoverflow ,您的程序将崩溃。但是,erlang消除了不是尾递归的递归函数的缺点。
答案 3 :(得分:0)
回答的问题并不是真正的答案,但出于格式化目的,我将其放在此处。在elixir中,我们通常使用Kernel.SpecialForms.for/1
理解来生成位串。
for << b :: size(1) <- "!!" >>, do: b
#⇒ [0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1]
for << b :: size(1) <- <<99>> >>, do: b
#⇒ [0, 1, 1, 0, 0, 0, 1, 1]
答案 4 :(得分:0)
我想使用8位二进制文件中的位来切换条件。所以
[b1, b2, ...] = extract(<<binary>>)
然后我想说:
if b1, do: x.... if b2, do: y...
是否有更好的方法来执行我要执行的操作,而不是执行模式 匹配?
首先,在长生不老药中,唯一评估为false的术语是false
和nil
(就像在ruby中一样):
iex(18)> x = 1
1
iex(19)> y = 0
0
iex(20)> if x, do: IO.puts "I'm true."
I'm true.
:ok
iex(21)> if y, do: IO.puts "I'm true."
I'm true.
:ok
尽管,修复很容易:
if b1 == 1, do: ...
将位提取到列表中是不必要的,因为您可以迭代位串:
def check_bits(<<>>), do: :ok
def check_bits(<<bit::integer-size(1), rest::bitstring>>) do
if bit == 1, do: IO.puts "bit is on"
check_bits(rest)
end
换句话说,您可以像对待列表一样对待位串。
或者,可以在函数的开头使用模式匹配,而不是在函数的主体中执行逻辑以确定位是否为1,
def check_bits(<<>>), do: :ok
def check_bits(<< 1::integer-size(1), rest::bitstring >>) do
IO.puts "The bit is 1."
check_bits(rest)
end
def check_bits(<< 0::integer-size(1), rest::bitstring >>) do
IO.puts "The bit is 0."
check_bits(rest)
end
代替使用变量bit
进行匹配,如下所示:
bit::integer-size(1)
...您使用文字值1
:
1::integer-size(1)
唯一可以匹配文字值的是文字值本身。结果,模式:
<< 1::integer-size(1), rest::bitstring >>
将仅匹配第一位integer-size(1)
为1
的位串。此处使用的 literal 匹配类似于对列表执行以下操作:
def list_contains_4([4|_tail]) do
IO.puts "found a 4"
true #end the recursion and return true
end
def list_contains_4([head|tail]) do
IO.puts "#{head} is not a 4"
list_contains_4(tail)
end
def list_contains_4([]), do: false
第一个函数子句尝试匹配列表开头的文字4
。如果列表的开头不是4
,则没有匹配项;因此,长生不老药进入下一个函数子句,并且在下一个函数子句中,变量head
将匹配列表中的所有内容。
在erlang中,使用函数开头的模式匹配而不是在函数主体中执行逻辑被认为是更时尚,更有效的方法。