【发布时间】:2021-04-15 22:54:02
【问题描述】:
如果我有字符串"UGGUGUUAUUAAUGGUUU",我如何将它变成一个列表,每3个字符分成["UGG", "UGU", "UAU", "UAA", "UGG", "UUU"]?
【问题讨论】:
标签: elixir
如果我有字符串"UGGUGUUAUUAAUGGUUU",我如何将它变成一个列表,每3个字符分成["UGG", "UGU", "UAU", "UAA", "UGG", "UUU"]?
【问题讨论】:
标签: elixir
如果您的字符串仅包含 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"
|> 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 != ""))
【讨论】:
unfold 就像一个有内存的iterate。
另一种可能是使用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"]
【讨论】: