【问题标题】:Pattern matching a bitstring using pin operator in Elixir在 Elixir 中使用 pin 运算符匹配位串的模式
【发布时间】:2023-03-19 00:27:01
【问题描述】:
# Erlang/OTP 24, Elixir 1.12.3

bmp_signature = <<66, 77>>
#=> "BM"

<<^bmp_signature, _::binary>> = <<66, 77, 30, 0>>
#=> ** (MatchError) no match of right hand side value: <<66, 77, 30, 0>>

为什么会这样?

简而言之,我想在一个循环中对位串进行模式匹配,而不是手动编写方法定义。所以不要这样:

@bmp_signature <<66, 77>>
…

def type(<<@bmp_signature, _::binary>>), do: :bmp
…

……类似这样的:

@signatures %{
  "bmp" => <<66, 77>>,
  …
}

def detect_type(file) do
  find_value_func = fn {extension, signature} ->
    case file do
      <<^signature, _::binary>> -> extension
      _ -> false
    end
  end

  Enum.find_value(@signatures, find_value_func)
end

没有元编程可以解决吗?

【问题讨论】:

    标签: elixir pattern-matching bitstring


    【解决方案1】:

    询问这在没有元编程的情况下是否可行,就像要求在没有Enum 模块和递归的情况下解决列表反向问题。

    中,有 元编程来解决这类任务。它使代码干净、简洁且易于管理。

    defmodule Type do
      @signatures %{
        bmp: <<66, 77>>
      }
    
      Enum.each(@signatures, fn {name, bom} ->
        def detect(<<unquote(bom), _::binary>>), do: unquote(name)
      end)
      def detect(_), do: nil
    end
    
    Type.detect(<<66, 77, 30, 0>>)
    #⇒ :bmp
    Type.detect(<<66, 30, 0>>)
    #⇒ nil
    

    不过,如果没有元编程,它仍然可以以一种丑陋而不是惯用的方式完成。

    defmodule Type do
      @signatures [
        {<<66, 77>>, :bmp},
        {<<77, 66>>, :pmb}
      ]
      
      def detect(signature, candidates \\ @signatures) do
        for <<c <- signature>>, reduce: @signatures do
          acc -> 
            Enum.reduce(acc, [], fn
              {"", name}, acc -> [{"", name} | acc]
              {<<^c, rest::binary>>, name}, acc -> [{rest, name} | acc]
              _, acc -> acc
            end)
        end
      end
    end
    
    case Type.detect(<<66, 77, 30, 0>>) do
      [{"", type}] -> {:ok, type}
      [] -> {:error, :none}
      [few] -> {:error, few: few}
    end
    #⇒ {:ok, :bmp}
    

    【讨论】:

    • 感谢您的回答。关于元编程解决方案。那么为每个签名动态定义一个方法就是“灵丹妙药”?如果有 100500 个签名怎么办,它仍然有效吗?我也相信您并不是要为 every 签名定义def detect(_), do: nil。以及您并不是说您的代码应该为相同的输入返回不同的结果:Type.detect(&lt;&lt;66, 77, 30, 0&gt;&gt;) #⇒ :bmp Type.detect(&lt;&lt;66, 77, 30, 0&gt;&gt;) #⇒ nil
    • def detect(_), do: nil 已修复,感谢您指出这一点。 “如果有 100500 个签名怎么办”——需要一个基准,但那些是文件签名。不会有 100500。
    【解决方案2】:

    你的语法有点不对劲。请记住 pin 运算符 ^ 仅固定一个值。在您的示例中,您试图将其固定为 2 个值。

    因此,如果您尝试匹配的对象是具有 2 个您知道的值的二进制文件,那么您需要将它们都固定,例如

    iex> <<bmp_sig1, bmp_sig2>> = <<66, 77>>
    iex> <<^bmp_sig1, ^bmp_sig2, rest::binary>> = <<66, 77, 88, 23, 44, 89>>
    <<66, 77, 88, 23, 44, 89>>
    iex> rest
    <<88, 23, 44, 89>>
    

    二进制语法&lt;&lt;&gt;&gt; 并不是唯一的方法——你可以用普通字符串来完成同样的事情(假设值实际上是字符串):

    iex> x = "apple"
    "apple"
    iex> "ap" <> rest = x
    "apple"
    iex> rest
    "ple"
    

    这里的问题是您不能 pin 前缀,因为您需要一个 literal 值才能进行匹配。这是因为事先不知道二进制文件的长度。

    如果您知道您的“签名”总是有 2、3 或 4 个字符,则可以对变量进行编码以适当地固定。但是,如果您必须处理未知长度,那么您可能需要依赖更类似于正则表达式或 String.starts_with?/2 或类似的东西。

    【讨论】:

    • 感谢您的回答。似乎 Erlang 在二进制模块中的 match/2 方法可以完成这项工作。
    • 太棒了。随意接受答案,或者如果您可以分享匹配的链接/2 我可以更新答案。
    【解决方案3】:

    Erlang 的 :binary.match/2 是适合这项工作的工具:

    defmodule Type do
      @signatures [
        {"bmp", <<66, 77>>},
        {"jpg", <<255, 216, 255>>},
      ]
    
      def detect(file_binary) do
        find_value_func = fn {extension, signature} ->
          case :binary.match(file_binary, signature) do
            {0, _} -> extension
            _ -> nil
          end
        end
    
        Enum.find_value(@signatures, find_value_func)
      end
    end
    
    Type.detect(<<66, 77, 0, 0>>) #=> "bmp"
    Type.detect(<<55, 66, 0, 0>>) #=> nil
    Type.detect(<<255, 216, 255, 0>>) #=> "jpg"
    

    非常感谢@Everett 提出的另寻他处的建议。

    【讨论】:

    • 在这里使用正则表达式是绝对的反模式。
    • 我想知道如何使用专门为此目的设计的 Erlang 的 binary 库是一种反模式,这与正则表达式有什么关系?这是来自文档的引用:“该模块包含用于操作面向字节的二进制文件的函数。尽管可以使用位语法提供大多数函数,但该库中的函数经过高度优化,预计执行速度更快或消耗更少内存,或两者兼而有之,而不是用纯 Erlang 编写的对应物。”
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-12-03
    • 1970-01-01
    • 2018-06-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多