2012-07-27 10 views
17

Tengo una expresión regular bastante simple, pero quería usar expresiones regulares con nombre para que sea más clara y luego repetir los resultados.Convertir coincidencias con nombre en MatchData a hash

sarta de pruebas:

testing_string = "111x222b333" 

Mi expresión regular:

regexp = %r{ 
       (?<width> [0-9]{3}) {0} 
       (?<height> [0-9]{3}) {0} 
       (?<depth> [0-9]+) {0} 

       \g<width>x\g<height>b\g<depth> 
      }x 
dimensions = regexp.match(testing_string) 

Este trabajo como un encanto, pero aquí está donde viene el problema:

dimensions.each { |k, v| dimensions[k] = my_operation(v) } 

# ERROR ! 

undefined method `each' for #<MatchData "111x222b333" width:"111" height:"222" depth:"333">. 

No hay. each método en el objeto MatchData, y realmente no quiero parchearlo.

¿Cómo puedo solucionar este problema?

No estaba tan claro como pensaba: el punto es guardar los nombres y la estructura tipo hash.

+0

Por favor, sea más claro la próxima vez. De lo contrario, respondedores como yo serán votados negativamente después de la aclaración :) – mmdemirbas

Respuesta

32

Si necesita un hash completo:

captures = Hash[ dimensions.names.zip(dimensions.captures) ] 
p captures 
#=> {"width"=>"111", "height"=>"222", "depth"=>"333"} 

Si lo que desea es iterar sobre los pares de nombre/valor:

dimensions.names.each do |name| 
    value = dimensions[name] 
    puts "%6s -> %s" % [ name, value ] 
end 
#=> width -> 111 
#=> height -> 222 
#=> depth -> 333 

Alternativas:

dimensions.names.zip(dimensions.captures).each do |name,value| 
    # ... 
end 

[ dimensions.names, dimensions.captures ].transpose.each do |name,value| 
    # ... 
end 

dimensions.names.each.with_index do |name,i| 
    value = dimensions.captures[i] 
    # ... 
end 
+0

Las versiones que usan dimensions.captures no funcionan si el mismo nombre se usa varias veces. El uso de 'dimensions [name]' devuelve la última coincidencia exitosa, como named_captures de Ruby 2.4. Utilizo lo siguiente para emular named_captures: 'matchdata.names.map {| n | [n, matchdata [n]]} .to_h' – oxc

-1

Si desea mantener los nombres, se puede hacer

new_dimensions = {} 
dimensions.names.each { |k| new_dimensions[k] = my_operation(dimensions[k]) } 
+0

Eso es exactamente lo que quiero, para guardar mis nombres. Su código no funcionará de todos modos, porque no hay ningún método [] = para MatchData. – blid

+0

Copié eso de tu código, porque no sabía lo que querías hacer con el hash. Modifiqué mi código para poner los resultados en un nuevo hash. Pero hacer 'dimensions.names.zip (dimensions.captures)' como sugiere Phrogz es aún más limpio. –

1

me ataco todo el problema de crear el hash un poco diferente:

irb(main):052:0> testing_string = "111x222b333" 
"111x222b333" 
irb(main):053:0> hash = Hash[%w[width height depth].zip(testing_string.scan(/\d+/))] 
{ 
    "width" => "111", 
    "height" => "222", 
    "depth" => "333" 
} 

Mientras expresiones regulares son de gran alcance, su sirena llamada puede ser muy atractivo, y dejarse atrapar por tratar de utilizarlos cuando hay formas más simples o sencillas de lograr algo. Es solo algo en lo que pensar


para realizar un seguimiento del número de elementos escaneados, por los PO Comentario:

hash = Hash[%w[width height depth].zip(scan_result = testing_string.scan(/\d+/))] 
=> {"width"=>"111", "height"=>"222", "depth"=>"333"} 
scan_result.size 
=> 3 

también hash.size volverá que, al igual que el tamaño de la matriz que contiene las llaves, etc.

+0

+1 Una buena sugerencia. – Phrogz

+0

Soy solo un tipo simple y mantener la expresión regular no es mi idea de diversión. :-) –

+0

Es una muy buena sugerencia, no solo en este caso, sino también en toda la programación de KISS. Trato de obedecer a la regla de simplicidad todo el tiempo, pero desafortunadamente en este caso necesito hacer un seguimiento de cuántos números obtengo cada vez. – blid

1

@Phrogz's answer es correcto si todas sus capturas tienen nombres únicos, pero puede dar varias capturas con el mismo nombre. Here's an example from the Regexp documentation.

Este código es compatible con capturas con nombres duplicados:

captures = Hash[ 
    dimensions.regexp.named_captures.map do |name, indexes| 
    [ 
     name, 
     indexes.map { |i| dimensions.captures[i - 1] } 
    ] 
    end 
] 

# Iterate over the captures 
captures.each do |name, values| 
    # name is a String 
    # values is an Array of Strings 
end 
3

Así hoy una nueva versión de Ruby (2.4.0), que was releasedincludes many new features, entre ellos feature #11999, también conocido como MatchData#named_captures.Esto significa que ahora puede hacer esto:

h = '12'.match(/(?<a>.)(?<b>.)(?<c>.)?/).named_captures 
#=> {"a"=>"1", "b"=>"2", "c"=>nil} 
h.class 
#=> Hash 

Así que en su cambio de código

dimensions = regexp.match(testing_string) 

a

dimensions = regexp.match(testing_string).named_captures 

Y puede utilizar el método each en el resultado del partido de expresiones regulares al igual que en cualquier otro Hash, también.

Cuestiones relacionadas