在Elixir

时间:2018-04-22 21:43:10

标签: elixir mask flags

我有十六进制格式的两个字节,如“0c”或“31”。 如何将其映射到某些标志值?我想要一个已设置的所有标志的列表。

示例:

"0c" -> 0000 1100 -> [:flag3, :flag2]
"31" -> 0011 0001 -> [:flag5, :flag4, :flag0]

这里每个标志都按其位置命名,但后来我希望标志具有更具描述性的名称。

从十六进制中获取很简单,只需String.to_integer("0c", 16),但之后我迷失在Bitwise的世界中。

3 个答案:

答案 0 :(得分:3)

我会选择二进制模式匹配:

等级“简单”。只是模式匹配。

<<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")被定义(为零或一)。

答案 1 :(得分:2)

@Igor为任意大小的输入发布了两个很好的解决方案但是如果你只有一个或一个固定数量的字节,你可以在一个衬里(这里我假设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之间的每个数字,我们检查该位是否设置为整数,如果是,则将其转换为原子并收集它。

答案 2 :(得分:1)

实际上,通过字符串操作可以避免“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