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.
5 Réponses :
Variante modifiée de @Phrogz answer
"aaabbbcccaaa".scan(/((.)\2*)/).map(&:first) # => ["aaa", "bbb", "ccc", "aaa"]
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.
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é.
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"]
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"]
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"]
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.
@Phrogz a donné il y a longtemps une réponse qui pourrait convenir à votre cas.