【问题标题】:Elixir: pattern matching two same arguments to a functionElixir:模式匹配两个相同的参数到一个函数
【发布时间】:2018-01-27 04:12:47
【问题描述】:

我正在遍历一个字符串列表,如果字符串的开头与提供的字符串匹配,我想返回它的内容。

例如

strings = [ "GITHUB:github.com", "STACKOVERFLOW:stackoverflow.com" ]
IO.puts fn(strings, "GITHUB") // => "github.com"

这是我目前的想法:

def get_tag_value([ << tag_name, ": ", tag_value::binary >> | rest ], tag_name), do: tag_value

def get_tag_value([ _ | rest], tag_name), do: get_tag_value(rest, tag_name)

def get_tag_value([], tag_name), do: ""

但我明白了:

** (CompileError) lib/file.ex:31: a binary field without size is only allowed at the end of a binary pattern and never allowed in binary generators

这是有道理的,但是我不太确定如何去做。如何将子字符串与作为参数提供的不同变量匹配?

【问题讨论】:

    标签: erlang elixir


    【解决方案1】:

    这就是我如何充分利用模式匹配而不调用String.starts_with?String.split

    defmodule A do
      def find(strings, string) do
        size = byte_size(string)
        Enum.find_value strings, fn
          <<^string::binary-size(size), ":", rest::binary>> -> rest
          _ -> nil
        end
      end
    end
    
    strings = ["GITHUB:github.com", "STACKOVERFLOW:stackoverflow.com"]
    
    IO.inspect A.find(strings, "GITHUB")
    IO.inspect A.find(strings, "STACKOVERFLOW")
    IO.inspect A.find(strings, "GIT")
    IO.inspect A.find(strings, "FOO")
    

    输出:

    "github.com"
    "stackoverflow.com"
    nil
    nil
    

    【讨论】:

    • 是否可以将 Enum.find_value 中的匿名函数写成非匿名函数?
    • 在看到你的回答后,我一直在尝试同样的功能,也传递了尺寸,但并没有真正让它发挥作用,例如def get_tag_value(list, tag_name), do: _get_tag_value, tag_name, byte_size(tag_name) 但我找不到让它工作的方法
    • 我认为不可能在函数头中做到这一点。 size 需要在函数之前定义,这意味着像我的答案这样的匿名函数或模块属性(此处不适用)。 def find_recursive(size, string, [&lt;&lt;string::size(size), rest::binary&gt;&gt; | tail]), do: rest 抛出编译错误:variable size@1 is unbound
    【解决方案2】:

    给这只猫剥皮的方法有很多。

    例如:

    def get_tag_value(tag, strings) do
      strings
      |> Enum.find("", &String.starts_with?(&1, tag <> ":"))
      |> String.split(":", parts: 2)
      |> Enum.at(1, "")
    end
    

    或者如果您仍想显式使用递归:

    def get_tag_value(_tag, []), do: ""
    
    def get_tag_value(tag, [str | rest]) do
      if String.starts_with?(str, tag <> ":") do
        String.split(str, ":", parts: 2) |> Enum.at(1, "")
      else
        get_tag_value(tag, rest)
      end
    end
    

    只是众多可能方式中的两种。

    但是,如果事先不知道(或至少知道长度),您将无法在函数头中对字符串进行模式匹配。

    【讨论】:

      【解决方案3】:
      iex(1)> strings = [ "GITHUB:github.com", "STACKOVERFLOW:stackoverflow.com" ]
      
      iex(2)> Enum.filter(strings, fn(s) -> String.starts_with?(s, "GITHUB") end)
      iex(3)> |> Enum.map(fn(s) -> [_, part_2] = String.split(s, ":"); part_2 end)
      
      # => ["github.com"]
      

      Enum.filter/2 中,我选择了所有以“GITHUB”开头的字符串,我得到了一个新的ListEnum.map/2 遍历新的 List 并在冒号处拆分每个字符串以仅返回第二部分。结果是一个List,所有部分都在冒号之后,其中原始字符串以“GITHUB”开头。

      请注意,如果有像“GITHUBgithub.com”这样的项目没有冒号,你会得到一个 MatchError。为了避免这种情况,要么使用String.starts_with?(s, "GITHUB:") 过滤正确的字符串,要么像我在Enum.map/2 中那样避免模式匹配,或者像@ryanwinchester 那样对空列表使用模式匹配。

      【讨论】:

        【解决方案4】:

        您可以结合使用 Enum.map 和 Enum.filter 来获取您正在寻找的匹配对:

        def get_tag_value(tag_name, tags) do
          tags
          |> Enum.map(&String.split(&1, ":")) # Creates a list of [tag_name, tag_value] elements
          |> Enum.filter(fn([tn, tv]) -> tn == tag_name end) # Filters for the tag name you're after
          |> List.last # Potentially gets you the pair [tag_name, tag_value] OR empty list
        end
        

        最后,您可以再次调用List.last/1 以获取空列表(未找到匹配项)或标记值。

        或者,您可以使用 case 语句返回不同类型的结果,例如 :nomatch 原子:

        def get_tag_value(tag_name, tags) do
          matches = tags
            |> Enum.map(&String.split(&1, ":")) # Creates a list of [tag_name, tag_value] elements
            |> Enum.filter(fn([tn, tv]) -> tn == tag_name end) # Filters for the tag name you're after
            |> List.last # Potentially gets you the pair [tag_name, tag_value] OR empty list
          case matches do
            [] -> :nomatch
            [_, tag_value] -> tag_value
          end
        end
        

        【讨论】:

          【解决方案5】:

          这是我对 Erlang 的看法:

          get_tag_value(Tag, Strings) ->
              L = size(Tag),
              [First | _] = [Val || <<Tag:L/binary, $:, Val/binary>> <- Strings]
              First.
          

          在 Elixir 中也是如此(可能还有更惯用的写法):

          def gtv(tag, strings) do
            l = :erlang.size(tag)
            [first | _ ] =
              for << t :: binary - size(l), ":", value :: binary >> <- strings,
                  t == tag,
                  do: value
            first
          end
          

          【讨论】:

            猜你喜欢
            • 2020-11-25
            • 2019-11-07
            • 2017-03-16
            • 2018-11-24
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-12-02
            相关资源
            最近更新 更多