2012-03-02 16 views
10

Como se establece básicamente en el título de la pregunta, ¿hay un método en Ruby Strings que sea equivalente a String#Scan pero en lugar de devolver solo una lista de cada coincidencia, devolvería una matriz de MatchData s? Por ejemplo:Ruby String # scan equivalent to return MatchData

# Matches a set of characters between underscore pairs 
"foo _bar_ _baz_ hashbang".some_method(/_[^_]+_/) #=> [#&ltMatchData "_bar_"&rt, &ltMatchData "_baz_"&rt] 

O de cualquier manera que pudiera obtener el mismo resultado o el mismo sería bueno. Me gustaría hacer esto para encontrar las posiciones y extensiones de "cadenas" dentro de las cadenas de Ruby, p. "goodbye y "world" dentro de "'adiós' cruel 'mundo'".

Respuesta

7

Puede construir fácilmente el suyo explotando MatchData#end y el parámetro pos de String#match. Algo como esto:

def matches(s, re) 
    start_at = 0 
    matches = [ ] 
    while(m = s.match(re, start_at)) 
     matches.push(m) 
     start_at = m.end(0) 
    end 
    matches 
end 

Y luego:

>> matches("foo _bar_ _baz_ hashbang", /_[^_]+_/) 
=> [#<MatchData "_bar_">, #<MatchData "_baz_">] 
>> matches("_a_b_c_", /_[^_]+_/) 
=> [#<MatchData "_a_">, #<MatchData "_c_">] 
>> matches("_a_b_c_", /_([^_]+)_/) 
=> [#<MatchData "_a_" 1:"a">, #<MatchData "_c_" 1:"c">] 
>> matches("pancakes", /_[^_]+_/) 
=> [] 

Usted podría parche mono que en cadena si realmente quería.

+0

Impresionante, ¡esto hace exactamente lo que necesito! Ja, yo estaba pensando en cómo podría hacer algo así, pero no sabía sobre el parámetro pos :) – Jwosty

11
memo = [] 
"foo _bar_ _baz_ hashbang".scan(/_[^_]+_/) { memo << Regexp.last_match } 
=> "foo _bar_ _baz_ hashbang" 
memo 
=> [#<MatchData "_bar_">, #<MatchData "_baz_">] 
+3

+1 Muy conciso. Y 'Regexp.last_match' es thread-local, por lo que no se encontrará en condiciones de carrera. – Kelvin

1

Si no necesita obtener MatchData S volver, aquí es una forma usando StringScanner.

require 'strscan' 

rxp = /_[^_]+_/ 
scanner = StringScanner.new "foo _barrrr_ _baz_ hashbang" 
match_infos = [] 
until scanner.eos? 
    scanner.scan_until rxp 
    if scanner.matched? 
    match_infos << { 
     pos: scanner.pre_match.size, 
     length: scanner.matched_size, 
     match: scanner.matched 
    } 
    else 
    break 
    end 
end 

p match_infos 
# [{:pos=>4, :length=>8, :match=>"_barrrr_"}, {:pos=>13, :length=>5, :match=>"_baz_"}]