web-dev-qa-db-fra.com

Elixir - Comment scinder une chaîne en une liste tous les 3 caractères

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"]?

10
Joseph An

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

Je me demande aussi s'il existe une version plus élégante

13
Joseph An

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 != ""))
9
michalmuskala

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
5
Patrick Oscity

S'il vous plaît essayez

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

Tu auras

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

Source de la documentation:

méthode de balayage

méthode d'aplatissement

4
Hubert Olender

Pourquoi ne pas utiliser String.split :

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