Correspondance de groupe Ruby Regexp, attribuer des variables sur 1 ligne

125

J'essaye actuellement de réexprimer une chaîne en plusieurs variables. Exemple de chaîne:

ryan_string = "RyanOnRails: This is a test"

Je l'ai mis en correspondance avec cette expression rationnelle, avec 3 groupes:

ryan_group = ryan_string.scan(/(^.*)(:)(.*)/i)

Maintenant, pour accéder à chaque groupe, je dois faire quelque chose comme ceci:

ryan_group[0][0] (first group) RyanOnRails
ryan_group[0][1] (second group) :
ryan_group[0][2] (third group) This is a test

Cela semble assez ridicule et j'ai l'impression de faire quelque chose de mal. Je m'attendrais à être capable de faire quelque chose comme ceci:

g1, g2, g3 = ryan_string.scan(/(^.*)(:)(.*)/i)

Est-ce possible? Ou y a-t-il une meilleure façon que la façon dont je le fais?

Ryanjones
la source

Réponses:

199

Vous ne voulez pas scande cela, car cela n'a pas de sens. Vous pouvez utiliser String#matchqui retournera un MatchDataobjet, vous pouvez ensuite appeler #capturespour renvoyer un tableau de captures. Quelque chose comme ça:

#!/usr/bin/env ruby

string = "RyanOnRails: This is a test"
one, two, three = string.match(/(^.*)(:)(.*)/i).captures

p one   #=> "RyanOnRails"
p two   #=> ":"
p three #=> " This is a test"

Sachez que si aucune correspondance n'est trouvée, String#matchretournera nil, donc quelque chose comme ça pourrait mieux fonctionner:

if match = string.match(/(^.*)(:)(.*)/i)
  one, two, three = match.captures
end

Bien que scancela ait peu de sens pour cela. Il fait toujours le travail, il vous suffit d'abord d'aplatir le tableau retourné.one, two, three = string.scan(/(^.*)(:)(.*)/i).flatten

Lee Jarvis
la source
6
Attention, si aucune correspondance n'est trouvée, la correspondance renvoie nil et vous obtenez une NilError. Si vous êtes dans Rails, je vous suggère de changer: one, two, three = string.match(/(^.*)(:)(.*)/i).captures en: one, two, three = string.match(/(^.*)(:)(.*)/i).try(:captures)
Andrea Salicetti
5
@AndreaSalicetti J'ai édité mon message, je n'y ajoute pas de code spécifique à Rails, je l'ai donc modifié avec une version pour gérer l'objet nil retourné
Lee Jarvis
3
Vous pouvez également le nouvel &.opérateur pour le remettre sur une ligne et même l'utiliser deux fois lorsqu'il n'y a qu'un seul groupe de capture. Par exemple.,string.match(regex)&.captures&.first
Gerry Shaw
46

Vous pouvez utiliser Match ou = ~ à la place, ce qui vous donnerait une seule correspondance et vous pourriez soit accéder aux données de correspondance de la même manière, soit simplement utiliser les variables de correspondance spéciales $ 1, $ 2, $ 3

Quelque chose comme:

if ryan_string =~ /(^.*)(:)(.*)/i
   first = $1
   third = $3
end
Rado
la source
5
@Gaston, c'est en fait la syntaxe d'expression régulière originale provenant de Perl :)
ohaleck
28

Vous pouvez nommer vos matchs capturés

string = "RyanOnRails: This is a test"
/(?<one>^.*)(?<two>:)(?<three>.*)/i =~ string
puts one, two, three

Cela ne fonctionne pas si vous inversez l'ordre de la chaîne et de l'expression régulière.

toonsend
la source
6

Vous devez décider si c'est une bonne idée, mais ruby ​​regexp peut (automatiquement) définir des variables locales pour vous!

Je ne sais pas encore si cette fonctionnalité est géniale ou tout simplement totalement folle, mais votre regex peut définir des variables locales.

ryan_string = "RyanOnRails: This is a test"
/^(?<webframework>.*)(?<colon>:)(?<rest>)/ =~ ryan_string
# This defined three variables for you. Crazy, but true.
webframework # => "RyanOnRails"
puts "W: #{webframework} , C: #{colon}, R: #{rest}"

(Jetez un œil à http://ruby-doc.org/core-2.1.1/Regexp.html , recherchez "variable locale").

Remarque: comme indiqué dans un commentaire, je vois qu'il existe une réponse similaire et antérieure à cette question de @toonsend ( https://stackoverflow.com/a/21412455 ). Je ne pense pas que je «volais», mais si vous voulez être juste avec les louanges et honorer la première réponse, n'hésitez pas :) J'espère qu'aucun animal n'a été blessé.

Félix
la source
Cette réponse ressemble remarquablement à stackoverflow.com/a/21412455/525478 , qui a plus d'un an de plus ...
Brad Werth
@BradWerth Je suppose que je n'ai tout simplement pas vu ça. Mais j'ai mis à jour ma réponse pour inclure vos préoccupations.
Felix
5

scan() trouvera toutes les correspondances non chevauchantes de l'expression régulière dans votre chaîne, donc au lieu de renvoyer un tableau de vos groupes comme vous semblez vous y attendre, il renvoie un tableau de tableaux.

Vous feriez probablement mieux d'utiliser match(), puis d'obtenir le tableau de captures en utilisant MatchData#captures:

g1, g2, g3 = ryan_string.match(/(^.*)(:)(.*)/i).captures

Cependant, vous pouvez également le faire avec scan()si vous le souhaitez:

g1, g2, g3 = ryan_string.scan(/(^.*)(:)(.*)/i)[0]
Andrew Clark
la source