【问题标题】:Elixir - How do I split a string into a list by every 3 charactersElixir - 如何按每 3 个字符将字符串拆分为列表
【发布时间】:2021-04-15 22:54:02
【问题描述】:

如果我有字符串"UGGUGUUAUUAAUGGUUU",我如何将它变成一个列表,每3个字符分成["UGG", "UGU", "UAU", "UAA", "UGG", "UUU"]

【问题讨论】:

    标签: elixir


    【解决方案1】:

    如果您的字符串仅包含 ASCII 字符,并且您的字符串的 byte_size 是 3 的倍数,则使用鲜为人知的 Elixir 功能有一个非常优雅的解决方案:二进制推导:

    iex(1)> string = "UGGUGUUAUUAAUGGUUU"
    "UGGUGUUAUUAAUGGUUU"
    iex(2)> for <<x::binary-3 <- string>>, do: x
    ["UGG", "UGU", "UAU", "UAA", "UGG", "UUU"]
    

    这会将字符串拆分为 3 个字节的块。这将比在代码点或字素上拆分要快得多,但如果您的字符串包含非 ASCII 字符,则无法正常工作。 (在这种情况下,我会选择@michalmuskala 的答案。)

    编辑:Patrick Oscity 的回答提醒我,这也适用于代码点:

    iex(1)> string = "αβγδεζηθικλμνξοπρςστυφχψ"
    "αβγδεζηθικλμνξοπρςστυφχψ"
    iex(2)> for <<a::utf8, b::utf8, c::utf8 <- string>>, do: <<a::utf8, b::utf8, c::utf8>>
    ["αβγ", "δεζ", "ηθι", "κλμ", "νξο", "πρς", "στυ", "φχψ"]
    

    【讨论】:

    • 通常你想在字素边界分割,而不仅仅是代码点。在很多情况下这会起作用,但它会破坏,例如,对于表情符号。例如,"ébcde"(第一个字母是两个代码点,e 和组合锐音)将变为 ["éb", "cde"]
    • 你让我感到羞耻,因为“UGGUGUUAUUAAUGGUUU”在测试套件中是完全匹配的……
    【解决方案2】:
    "UGGUGUUAUUAAUGGUUU"
    |> String.codepoints
    |> Enum.chunk_every(3)
    |> Enum.map(&Enum.join/1)
    

    我也想知道有没有更优雅的版本

    【讨论】:

      【解决方案3】:

      这可以使用Stream.unfold/2 函数来实现。在某种程度上,它与reduce 正好相反——reduce 允许我们将集合折叠为单个值,展开是将单个值展开为集合。

      作为Stream.unfold/2 的生成器,我们需要一个返回元组的函数——第一个元素是生成集合的下一个成员,第二个元素是我们要传递到下一次迭代的累加器。这准确描述了函数String.split_at/2。最后,我们需要一个终止条件——String.split_at("", 3) 将返回{"", ""}。我们对空字符串不感兴趣,所以处理我们生成的流直到遇到空字符串就足够了——这可以通过Enum.take_while/2 来实现。

      string
      |> Stream.unfold(&String.split_at(&1, 3)) 
      |> Enum.take_while(&(&1 != ""))
      

      【讨论】:

      • 一个非常好的用例,我还看到unfold 就像一个有内存的iterate
      【解决方案4】:

      另一种可能是使用Regex.scan/2

      iex> string = "abcdef"
      iex> Regex.scan(~r/.{3}/, string)
      [["abc"], ["def"]]
      
      # In case the number of characters is not evenly divisible by 3
      iex> string = "abcdefg"
      iex> Regex.scan(~r/.{1,3}/, string)
      [["abc"], ["def"], ["g"]]
      
      # If you need to handle unicode characters, you can add the `u` modifier
      iex> string = "???abc"
      iex> Regex.scan(~r/.{1,3}/u, string)
      [["???"], ["abc"]]
      

      或者使用递归函数,这有点冗长,但 IMO 应该是使用热切评估的最佳性能解决方案:

      defmodule Split do
        def tripels(string), do: do_tripels(string, [])
      
        defp do_tripels(<<x::utf8, y::utf8, z::utf8, rest::binary>>, acc) do
          do_tripels(rest, [<<x::utf8, y::utf8, z::utf8>> | acc])
        end
      
        defp do_tripels(_rest, acc) do
          Enum.reverse(acc)
        end
      end
      
      #  in case you actually want the rest in the result, change the last clause to
      defp do_tripels(rest, acc) do
        Enum.reverse([rest | acc])
      end
      

      【讨论】:

        【解决方案5】:

        请尝试

        List.flatten(Regex.scan(~r/.../, "UGGUGUUAUUAAUGGUUU"))
        

        你会得到

        ["UGG", "UGU", "UAU", "UAA", "UGG", "UUU"]
        

        资料来源:

        scan method

        flatten method

        【讨论】:

        • 干净优雅,我喜欢!
        【解决方案6】:

        String.split怎么样:

        iex> String.split("UGGUGUUAUUAAUGGUUU", ~r/.{3}/, include_captures: true, trim: true)
        ["UGG", "UGU", "UAU", "UAA", "UGG", "UUU"]
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2019-04-16
          • 1970-01-01
          • 2019-06-27
          • 2020-06-21
          • 1970-01-01
          • 1970-01-01
          • 2015-09-01
          • 2011-11-13
          相关资源
          最近更新 更多