繁体   English   中英

Elixir - 如何将字符串每 3 个字符拆分为一个列表

[英]Elixir - How do I split a string into a list by every 3 characters

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

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

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>>
["αβγ", "δεζ", "ηθι", "κλμ", "νξο", "πρς", "στυ", "φχψ"]
"UGGUGUUAUUAAUGGUUU"
|> String.codepoints
|> Enum.chunk_every(3)
|> Enum.map(&Enum.join/1)

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

这可以使用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 != ""))

另一种可能性是使用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

请尝试

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

你会得到

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

来自文档的来源:

扫描方式

压平方法

如何使用String.split

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM