从 Elixir 中的字节解析标志
Parsing flags from bytes in Elixir
我有两个十六进制格式的字节,例如“0c”或“31”。
我如何将其映射到某些标志值?我想要一个包含所有已设置标志的列表。
示例:
"0c" -> 0000 1100 -> [:flag3, :flag2]
"31" -> 0011 0001 -> [:flag5, :flag4, :flag0]
这里每个旗帜都是按其位置命名的,但稍后我希望旗帜的名称更具描述性。
从十六进制开始很容易,只是 String.to_integer("0c", 16)
,但在那之后我迷失在 Bitwise
的世界里。
实际上"the world of Bitwise
"可以通过字符串操作来避免:
"0c"
|> String.to_integer(16) # 12
|> Integer.to_string(2) # "1100"
|> String.codepoints # ["1", "1", "0", "0"]
|> Enum.reverse # ["0", "0", "1", "1"]
|> Enum.with_index # [{"0", 0}, {"0", 1}, {"1", 2}, {"1", 3}]
|> Enum.reduce([], fn
{"1", index}, acc -> [:"flag#{index}" | acc]
_, acc -> acc
end) # [:flag3, :flag2]
否则,可以这样计算:
defmodule FlagBuilder do
use Bitwise
def build_flags(number, index \ 0)
def build_flags(0, _) do
[]
end
def build_flags(number, index) do
next = fn -> build_flags(number >>> 1, index + 1) end
case number &&& 1 do
0 -> next.()
1 -> [:"flag#{index}" | next.()]
end
end
end
a = "31"
|> String.to_integer(16) # 12
|> FlagBuilder.build_flags
想法是找出最后一位(通过使用 &&& 1
)并构建一个标志,如果最后一位是 1
。下一次迭代给出的数字是在该位上向右移动的输入数字(通过使用 >>> 1
)
@Igor 已经为任意大小的输入发布了两个很好的解决方案,但是如果你只有 1 个或固定数量的字节,你可以在一行中完成(这里我假设 1 个字节/8 位输入; 只需将 n
更改为您要检查的位数(如果有更多)):
for i <- 0..7, (n >>> i &&& 1) == 1, do: :"flag#{i}"
iex(1)> use Bitwise
Bitwise
iex(2)> n = "31" |> String.to_integer(16)
49
iex(3)> for i <- 0..7, (n >>> i &&& 1) == 1, do: :"flag#{i}"
[:flag0, :flag4, :flag5]
iex(4)> n = "0c" |> String.to_integer(16)
12
iex(5)> for i <- 0..7, (n >>> i &&& 1) == 1, do: :"flag#{i}"
[:flag2, :flag3]
对于从 0 到 7 的每个数字,我们检查整数中是否设置了该位,如果是,则将其转换为原子并收集。
我会选择二进制模式匹配:
“简单”级别。只是模式匹配。
<<i1::1,i2::1,i3::1,i4::1,i5::1,i6::1,i7::1,i8::1>> =
<<String.to_integer("0c", 16)>>
#⇒ "\f"
{i1,i2,i3,i4,i5,i6,i7,i8}
#⇒ {0, 0, 0, 0, 1, 1, 0, 0}
是的,我们已经将所有标志(为简洁起见在上面命名为 i
)开箱即用。
将它们转换为 :flagN
个原子的列表:
[i1,i2,i3,i4,i5,i6,i7,i8]
|> Enum.reverse()
|> Enum.with_index()
|> Enum.reduce([], fn
{0, _}, acc -> acc
{_, idx}, acc -> [:"flag#{idx}" | acc]
end)
[:flag3, :flag2]
“中级”水平。创建一个接受 String
并生成元组的函数。
def flags(input) do
# possibly some checks of input to fail fast
<<i1::1,i2::1,i3::1,i4::1,i5::1,i6::1,i7::1,i8::1>> =
<<String.to_integer(input, 16)>>
{i1,i2,i3,i4,i5,i6,i7,i8}
end
“高级”级别。生成一个将为任意长度的输入生成函数的宏(或者直接在模块主体中生成函数。)
defmodule Flags do
Enum.each(1..10, fn i ->
# generate a function for `String`s of length 1–10 here
end)
end
奖金轨道。 导出 flag0
.. flag7
变量绕过当前上下文 macros hygiene:
defmodule Flags do
defmacro flags(input) do
mapper =
{:<<>>, [],
0..7
|> Enum.map(& {:::, [], [{:var!, [context: Elixir, import: Kernel],
[{:"flag#{&1}", [], Elixir}]}, 1]})
|> Enum.reverse()
}
quote do
unquote(mapper) = <<String.to_integer(unquote(input), 16)>>
end
end
end
defmodule Flags.Test do
import Flags
def test do
flags("0c")
[flag0,flag1,flag2,flag3,flag4,flag5,flag6,flag7]
|> Enum.with_index()
|> Enum.reduce([], fn
{0, _}, acc -> acc
{_, idx}, acc -> [:"flag#{idx}" | acc]
end)
|> IO.inspect(label: "Result")
IO.inspect(flag2, label: "Flag2")
end
end
Flags.Test.test
#⇒ Result: [:flag3, :flag2]
# Flag2: 1
在后一个示例中,有 局部变量 flagN
在调用 flags("0c")
后被定义(为零或一)。
我有两个十六进制格式的字节,例如“0c”或“31”。 我如何将其映射到某些标志值?我想要一个包含所有已设置标志的列表。
示例:
"0c" -> 0000 1100 -> [:flag3, :flag2]
"31" -> 0011 0001 -> [:flag5, :flag4, :flag0]
这里每个旗帜都是按其位置命名的,但稍后我希望旗帜的名称更具描述性。
从十六进制开始很容易,只是 String.to_integer("0c", 16)
,但在那之后我迷失在 Bitwise
的世界里。
实际上"the world of Bitwise
"可以通过字符串操作来避免:
"0c"
|> String.to_integer(16) # 12
|> Integer.to_string(2) # "1100"
|> String.codepoints # ["1", "1", "0", "0"]
|> Enum.reverse # ["0", "0", "1", "1"]
|> Enum.with_index # [{"0", 0}, {"0", 1}, {"1", 2}, {"1", 3}]
|> Enum.reduce([], fn
{"1", index}, acc -> [:"flag#{index}" | acc]
_, acc -> acc
end) # [:flag3, :flag2]
否则,可以这样计算:
defmodule FlagBuilder do
use Bitwise
def build_flags(number, index \ 0)
def build_flags(0, _) do
[]
end
def build_flags(number, index) do
next = fn -> build_flags(number >>> 1, index + 1) end
case number &&& 1 do
0 -> next.()
1 -> [:"flag#{index}" | next.()]
end
end
end
a = "31"
|> String.to_integer(16) # 12
|> FlagBuilder.build_flags
想法是找出最后一位(通过使用 &&& 1
)并构建一个标志,如果最后一位是 1
。下一次迭代给出的数字是在该位上向右移动的输入数字(通过使用 >>> 1
)
@Igor 已经为任意大小的输入发布了两个很好的解决方案,但是如果你只有 1 个或固定数量的字节,你可以在一行中完成(这里我假设 1 个字节/8 位输入; 只需将 n
更改为您要检查的位数(如果有更多)):
for i <- 0..7, (n >>> i &&& 1) == 1, do: :"flag#{i}"
iex(1)> use Bitwise
Bitwise
iex(2)> n = "31" |> String.to_integer(16)
49
iex(3)> for i <- 0..7, (n >>> i &&& 1) == 1, do: :"flag#{i}"
[:flag0, :flag4, :flag5]
iex(4)> n = "0c" |> String.to_integer(16)
12
iex(5)> for i <- 0..7, (n >>> i &&& 1) == 1, do: :"flag#{i}"
[:flag2, :flag3]
对于从 0 到 7 的每个数字,我们检查整数中是否设置了该位,如果是,则将其转换为原子并收集。
我会选择二进制模式匹配:
“简单”级别。只是模式匹配。
<<i1::1,i2::1,i3::1,i4::1,i5::1,i6::1,i7::1,i8::1>> =
<<String.to_integer("0c", 16)>>
#⇒ "\f"
{i1,i2,i3,i4,i5,i6,i7,i8}
#⇒ {0, 0, 0, 0, 1, 1, 0, 0}
是的,我们已经将所有标志(为简洁起见在上面命名为 i
)开箱即用。
将它们转换为 :flagN
个原子的列表:
[i1,i2,i3,i4,i5,i6,i7,i8]
|> Enum.reverse()
|> Enum.with_index()
|> Enum.reduce([], fn
{0, _}, acc -> acc
{_, idx}, acc -> [:"flag#{idx}" | acc]
end)
[:flag3, :flag2]
“中级”水平。创建一个接受 String
并生成元组的函数。
def flags(input) do
# possibly some checks of input to fail fast
<<i1::1,i2::1,i3::1,i4::1,i5::1,i6::1,i7::1,i8::1>> =
<<String.to_integer(input, 16)>>
{i1,i2,i3,i4,i5,i6,i7,i8}
end
“高级”级别。生成一个将为任意长度的输入生成函数的宏(或者直接在模块主体中生成函数。)
defmodule Flags do
Enum.each(1..10, fn i ->
# generate a function for `String`s of length 1–10 here
end)
end
奖金轨道。 导出 flag0
.. flag7
变量绕过当前上下文 macros hygiene:
defmodule Flags do
defmacro flags(input) do
mapper =
{:<<>>, [],
0..7
|> Enum.map(& {:::, [], [{:var!, [context: Elixir, import: Kernel],
[{:"flag#{&1}", [], Elixir}]}, 1]})
|> Enum.reverse()
}
quote do
unquote(mapper) = <<String.to_integer(unquote(input), 16)>>
end
end
end
defmodule Flags.Test do
import Flags
def test do
flags("0c")
[flag0,flag1,flag2,flag3,flag4,flag5,flag6,flag7]
|> Enum.with_index()
|> Enum.reduce([], fn
{0, _}, acc -> acc
{_, idx}, acc -> [:"flag#{idx}" | acc]
end)
|> IO.inspect(label: "Result")
IO.inspect(flag2, label: "Flag2")
end
end
Flags.Test.test
#⇒ Result: [:flag3, :flag2]
# Flag2: 1
在后一个示例中,有 局部变量 flagN
在调用 flags("0c")
后被定义(为零或一)。