Ruby Regexp gruppo di corrispondenza, assegna variabili su 1 riga


125

Attualmente sto cercando di rexp una stringa in più variabili. Stringa di esempio:

ryan_string = "RyanOnRails: This is a test"

L'ho abbinato a questa regexp, con 3 gruppi:

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

Ora per accedere a ogni gruppo devo fare qualcosa del genere:

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

Sembra abbastanza ridicolo e mi sembra di fare qualcosa di sbagliato. Mi aspetterei di poter fare qualcosa del genere:

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

È possibile? O c'è un modo migliore di come lo sto facendo?

Risposte:


199

Non vuoi scanper questo, perché ha poco senso. Puoi usare String#matchche restituirà un MatchDataoggetto, puoi quindi chiamare #capturesper restituire un array di acquisizioni. Qualcosa come questo:

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

Tieni presente che se non viene trovata alcuna corrispondenza, String#matchrestituirà zero, quindi qualcosa di simile potrebbe funzionare meglio:

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

Anche se scannon ha molto senso per questo. Fa ancora il lavoro, devi solo appiattire prima l'Array restituito.one, two, three = string.scan(/(^.*)(:)(.*)/i).flatten


6
Fai attenzione che se non vengono trovate corrispondenze, match restituisce zero e ottieni NilError. Se sei in Rails, ti consiglio di cambiare: one, two, three = string.match(/(^.*)(:)(.*)/i).captures in: one, two, three = string.match(/(^.*)(:)(.*)/i).try(:captures)
Andrea Salicetti

5
@AndreaSalicetti Ho modificato il mio post, non sto aggiungendo codice specifico per Rails, quindi l'ho modificato con una versione per la gestione dell'oggetto nullo restituito
Lee Jarvis

3
È anche possibile che il nuovo &.operatore lo rimetta in linea e lo utilizzi anche due volte quando è presente un solo gruppo di acquisizione. Ad esempio,string.match(regex)&.captures&.first
Gerry Shaw

46

Potresti usare Match o = ~ invece che ti darebbe una singola corrispondenza e potresti accedere ai dati della corrispondenza nello stesso modo o semplicemente utilizzare le variabili di corrispondenza speciali $ 1, $ 2, $ 3

Qualcosa di simile a:

if ryan_string =~ /(^.*)(:)(.*)/i
   first = $1
   third = $3
end

5
@Gaston che in realtà è la sintassi regexp originale proveniente da Perl :)
ohaleck

28

Puoi assegnare un nome alle corrispondenze acquisite

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

Non funziona se inverti l'ordine della stringa e della regex.


6

Devi decidere se è una buona idea, ma regexp ruby ​​può (automagicamente) definire variabili locali per te!

Non sono ancora sicuro se questa funzione sia fantastica o semplicemente totalmente pazza, ma la tua regex può definire variabili locali.

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

(Dai un'occhiata a http://ruby-doc.org/core-2.1.1/Regexp.html , cerca "variabile locale").

Nota: come sottolineato in un commento, vedo che esiste una risposta simile e precedente a questa domanda di @toonsend ( https://stackoverflow.com/a/21412455 ). Non penso di aver "rubato", ma se vuoi essere onesto con le lodi e onorare la prima risposta, sentiti libero :) Spero che nessun animale sia stato danneggiato.


Questa risposta sembra notevolmente simile a stackoverflow.com/a/21412455/525478 , che ha più di un anno in più ...
Brad Werth

@ BradWerth credo di non averlo visto. Ma ho aggiornato la mia risposta per includere le tue preoccupazioni.
Felix

5

scan() troverà tutte le corrispondenze non sovrapposte della regex nella stringa, quindi invece di restituire un array dei tuoi gruppi come ti aspetti, restituisce un array di array.

Probabilmente è meglio usare match()e quindi ottenere la serie di acquisizioni utilizzando MatchData#captures:

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

Tuttavia potresti farlo anche con scan()se desideri:

g1, g2, g3 = ryan_string.scan(/(^.*)(:)(.*)/i)[0]
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.