Correspondência de grupo Ruby Regexp, atribua variáveis ​​em 1 linha

125

Atualmente, estou tentando rexp uma seqüência de caracteres em várias variáveis. Exemplo de sequência:

ryan_string = "RyanOnRails: This is a test"

Combinei com este regexp, com 3 grupos:

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

Agora, para acessar cada grupo, tenho que fazer algo assim:

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

Isso parece bastante ridículo e parece que estou fazendo algo errado. Eu esperaria poder fazer algo assim:

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

Isso é possível? Ou existe uma maneira melhor do que como estou fazendo isso?

ryanjones
fonte

Respostas:

199

Você não quer scanisso, pois faz pouco sentido. Você pode usar o String#matchque retornará um MatchDataobjeto e, em seguida, chamar #capturespara retornar uma matriz de capturas. Algo assim:

#!/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"

Esteja ciente de que, se nenhuma correspondência for encontrada, String#matchretornará nulo; portanto, algo assim pode funcionar melhor:

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

Embora scanfaça pouco sentido para isso. Ele ainda faz o trabalho, basta achatar a matriz retornada primeiro.one, two, three = string.scan(/(^.*)(:)(.*)/i).flatten

Lee Jarvis
fonte
6
Cuidado, se nenhuma correspondência for encontrada, a correspondência retornará zero e você obterá um NilError. Se você está no Rails, sugiro que mude: one, two, three = string.match(/(^.*)(:)(.*)/i).captures para: one, two, three = string.match(/(^.*)(:)(.*)/i).try(:captures)
Andrea Salicetti 25/01
5
@AndreaSalicetti Eu editei o meu post, eu não estou adicionando código Rails específico-a ele, então eu ter alterado-lo com uma versão para manusear o objeto nulo voltou
Lee Jarvis
3
Você também pode &.usar o novo operador para recuperá-lo em uma linha e até usá-lo duas vezes quando houver apenas um grupo de captura. Ex ..,string.match(regex)&.captures&.first
Gerry Shaw
46

Você pode usar Match ou = ~ em vez disso, o que forneceria uma única correspondência e você poderia acessar os dados da correspondência da mesma maneira ou apenas usar as variáveis ​​de correspondência especiais $ 1, $ 2, $ 3

Algo como:

if ryan_string =~ /(^.*)(:)(.*)/i
   first = $1
   third = $3
end
Rado
fonte
5
@Gaston que é realmente a sintaxe regexp originais provenientes de Perl :)
ohaleck
28

Você pode nomear suas correspondências capturadas

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

Não funciona se você reverter a ordem da sequência e a expressão regular.

enviar
fonte
6

Você precisa decidir se é uma boa ideia, mas o ruby ​​regexp pode (automagicamente) definir variáveis ​​locais para você!

Ainda não tenho certeza se esse recurso é incrível ou apenas totalmente louco, mas seu regex pode definir variáveis ​​locais.

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}"

(Dê uma olhada em http://ruby-doc.org/core-2.1.1/Regexp.html , procure por "variável local").

Nota: Como indicado em um comentário, vejo que há uma resposta semelhante e anterior a esta pergunta por @toonsend ( https://stackoverflow.com/a/21412455 ). Eu não acho que estava "roubando", mas se você quiser ser justo com elogios e honrar a primeira resposta, fique à vontade :) Espero que nenhum animal tenha sido ferido.

Felix
fonte
Esta resposta muito parecido com stackoverflow.com/a/21412455/525478 , que é mais de um ano mais velho ...
Brad Werth
@ BradWerth Acho que simplesmente não vi isso. Mas atualizei minha resposta para incluir suas preocupações.
Felix
5

scan() encontrará todas as correspondências não sobrepostas da regex na sua string. Portanto, em vez de retornar uma matriz de seus grupos como você espera, ela retornará uma matriz de matrizes.

Provavelmente, é melhor usar match()e obter a matriz de capturas usando MatchData#captures:

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

No entanto, você também pode fazer isso scan()se quiser:

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