1
votes

Ruby: Comment puis-je diviser une chaîne contenant des groupes de lettres consécutives en groupes de ces lettres?

Je veux transformer une chaîne contenant des groupes de lettres consécutives:

["aaa","bbb","ccc","aaa"]

En:

"aaabbbcccaaa"

Je suis sûr que cela devrait être simple en Ruby mais je suis perplexe.


1 commentaires

@Phrogz a donné il y a longtemps une réponse qui pourrait convenir à votre cas.


5 Réponses :


0
votes

Variante modifiée de @Phrogz answer

"aaabbbcccaaa".scan(/((.)\2*)/).map(&:first) # => ["aaa", "bbb", "ccc", "aaa"]


0 commentaires

4
votes
str = "aaabbbcccaaa"

str.gsub(/(.)\1*/).to_a
  #=> ["aaa", "bbb", "ccc", "aaa"]
This uses the form of String#gsub that does not have a block and therefore returns an enumerator.

3 commentaires

Je comprends l'expression régulière utilisée ici comme suit: correspond à un seul caractère suivi de zéro ou plus de la même correspondance exacte . Mais ce que je ne comprends pas, c'est pourquoi cela fonctionne dans #gsub mais pas dans #scan . Si quelqu'un pouvait m'aider à comprendre pourquoi, je l'apprécierais.


C'est simplement parce que String # scan traite la capture groupes d'une manière spéciale. Voir la doc. Est-ce clair? Parfois, il est utile que scan ait cette propriété; d'autres fois, cela gêne.


Je viens de remarquer que j'avais inclus \ 1 dans un groupe de capture inutile. Je ne sais pas comment cela s'est passé (ou pourquoi quelqu'un d'autre ne l'a pas signalé), mais je l'ai corrigé.



0
votes

Cette variante devrait fonctionner sur n'importe quelle chaîne avec des groupes de 2 caractères consécutifs

"foo\n\n??barr..bazz".gsub(/([a-zA-Z])(\1)*/).select{|l| l.length >1}
#=> ["oo", "rr", "zz"]

Ou pour les caractères alpha uniquement:

"foo\n\nbarr".gsub(/(.)(\1)*/).select{|l| l.length >1}
#=> ["oo", "rr"]


0 commentaires

0
votes

Version idiote non-regex:

str = "aaabbbcccaaa"
str.each_char.with_object([]) { |a,r| (r.last&.end_with?(a) ? r.last : r) << a }
=> ["aaa", "bbb", "ccc", "aaa"]


0 commentaires

0
votes

J'ai trouvé un moyen de le faire sans regex en utilisant Array#slice_when:

Benchmark.measure do
  100_000.times { "AAAABBBCDDD".chars.slice_when(&:!=).map(&:join) }  
end  

# => #<Benchmark::Tms:0x00007fb11ff9a560
#  @cstime=0.0,
#  @cutime=0.0,
#  @label="",
#  @real=1.427345999982208,
#  @stime=0.013876,
#  @total=1.3629069999999996,
#  @utime=1.3490309999999996>

Benchmark.measure do
  100_000.times { "AAAABBBCDDD".gsub(/(.)(\1)*/).to_a }
end

=> #<Benchmark::Tms:0x00007fb1214f4dc0
#  @cstime=0.0,
#  @cutime=0.0,
#  @label="",
#  @real=0.6837240000022575,
#  @stime=0.03575100000000003,
#  @total=0.64306,
#  @utime=0.6073089999999999>

Bien que gsub avec une regex ( comme suggéré par Cary Swoveland dans sa réponse) est clairement plus rapide:

"AAAABBBCDDD".chars.slice_when(&:!=).map(&:join)
#=> ["AAAA", "BBB", "C", "DDD"]


1 commentaires

Notez que dans ma réponse /(.)\1*/ est suffisant. Je ne sais pas comment ce deuxième groupe de capture a trouvé sa place dans ma réponse originale.