si j'ai la chaîne "UGGUGUUAUUAAUGGUUU"
, comment puis-je la transformer en une liste divisée par tous les 3 caractères en ["UGG", "UGU", "UAU", "UAA", "UGG", "UUU"]
?
Si votre chaîne ne contient que ASCII caractères et que le byte_size
de votre chaîne est un multiple de 3, il existe une solution très élégante utilisant une fonctionnalité Elixir moins connue: compréhensions binaires:
iex(1)> string = "UGGUGUUAUUAAUGGUUU"
"UGGUGUUAUUAAUGGUUU"
iex(2)> for <<x::binary-3 <- string>>, do: x
["UGG", "UGU", "UAU", "UAA", "UGG", "UUU"]
Cela divise la chaîne en morceaux de 3 octets. Cela sera beaucoup plus rapide que le fractionnement en points de code ou en graphèmes, mais ne fonctionnera pas correctement si votre chaîne contient des caractères non-ASCII. (Dans ce cas, j'irais avec la réponse de @ michalmuskala.)
Edit: La réponse de Patrick Oscity m'a rappelé que cela peut également fonctionner pour les points de code:
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(3)
|> Enum.map(&Enum.join/1)
Je me demande aussi s'il existe une version plus élégante
Ceci peut être réalisé en utilisant la fonction Stream.unfold/2
. D'une certaine manière, c'est l'opposé de reduce
- reduction nous permet de réduire une collection en une seule valeur. Le déploiement consiste à développer une seule valeur dans une collection.
En tant que générateur pour Stream.unfold/2
, nous avons besoin d'une fonction qui retourne un tuple - le premier élément est le prochain membre de la collection générée et le second est l'accumulateur que nous allons passer à la prochaine itération. Ceci décrit exactement la fonction String.split_at/2
. Enfin, nous avons besoin d’une condition de terminaison - String.split_at("", 3)
renverra {"", ""}
. Nous ne sommes pas intéressés par les chaînes vides, il devrait donc suffire de traiter notre flux généré jusqu'à ce que nous rencontrions la chaîne vide - ceci peut être réalisé avec Enum.take_while/2
.
string
|> Stream.unfold(&String.split_at(&1, 3))
|> Enum.take_while(&(&1 != ""))
Une autre possibilité serait d'utiliser 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"]]
Ou en utilisant une fonction récursive, qui est un peu verbeuse mais devrait être la solution la plus performante utilisant une évaluation rapide:
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
S'il vous plaît essayez
List.flatten(Regex.scan(~r/.../, "UGGUGUUAUUAAUGGUUU"))
Tu auras
["UGG", "UGU", "UAU", "UAA", "UGG", "UUU"]
Source de la documentation:
Pourquoi ne pas utiliser String.split :
iex> String.split("UGGUGUUAUUAAUGGUUU", ~r/.{3}/, include_captures: true, trim: true)
["UGG", "UGU", "UAU", "UAA", "UGG", "UUU"]