2009-11-30 13 views
18

Estoy tratando de devolver el índice a todas las apariciones de un carácter específico en una cadena con Ruby. Una cadena de ejemplo es "a#asg#sdfg#d##" y el retorno esperado es [1,5,10,12,13] al buscar # caracteres. El siguiente código hace el trabajo, pero debe haber una forma más simple de hacer esto.Índice de devolución de todas las apariciones de un carácter en una cadena en ruby ​​

def occurances (line) 

    index = 0 
    all_index = [] 

    line.each_byte do |x| 
    if x == '#'[0] then 
     all_index << index 
    end 
    index += 1 
    end 

    all_index 
end 

Respuesta

15
s = "a#asg#sdfg#d##" 
a = (0 ... s.length).find_all { |i| s[i,1] == '#' } 
+3

s = "a # asg # sdfg # d ##" a = (0 ... s.length) .find_all {| i | s [i] == '#'} debería funcionar también ¿no? sin necesidad de, 1 ...? –

+0

@SamJoseph En este caso, sí, los dos son sinónimos. La versión de 2 argumentos de '[x, y]' significa "una subcadena de longitud' y' que comienza en 'x'", que es lo mismo que '[x]', que significa "carácter en' x' (también un cadena porque ruby ​​no tiene un tipo de Char) ". – erich2k8

15
require 'enumerator' # Needed in 1.8.6 only 
"1#3#a#".enum_for(:scan,/#/).map { Regexp.last_match.begin(0) } 
#=> [1, 3, 5] 

ETA: Esto funciona mediante la creación de un enumerador que utiliza como su scan(/#/) cada método.

escaneo produce cada ocurrencia del patrón especificado (en este caso /#/) y dentro del bloque puede llamar a Regexp.last_match para acceder al objeto MatchData para la coincidencia.

MatchData#begin(0) devuelve el índice donde comienza la coincidencia y, como utilizamos el mapa en el enumerador, obtenemos una matriz de esos índices.

+1

Genial, pero no estoy seguro de cómo funciona esto. – Gerhard

2

Aquí hay una cadena larga método:

"a#asg#sdfg#d##". 
    each_char. 
    each_with_index. 
    inject([]) do |indices, (char, idx)| 
    indices << idx if char == "#" 
    indices 
    end 

# => [1, 5, 10, 12, 13] 

requiere 1.8.7+

+0

En 1.9 puedes hacer '.each_char.with_index' (en lugar de' each_char.each_with_index'). Se lee mejor de esa manera, creo. – Telemachus

+0

lo hace de hecho. –

12

Aquí está una manera menos suposición:

i = -1 
all = [] 
while i = x.index('#',i+1) 
    all << i 
end 
all 

En una prueba de velocidad rápida esto era aproximadamente 3.3 veces más rápido que el método find_all de FM, y aproximadamente 2.5 veces más rápido que el método enum_for de sepp2k.

+0

Esas cifras de velocidad eran de 1.8.5. En 1.9.1, este sigue siendo el más rápido por un amplio margen, pero find_all es aproximadamente 3 veces más lento y enum_for es aproximadamente 5 veces más lento. –

+0

Mi suposición rápida es que 'Regexp.last_match.begin (0)' está frenando el método 'enum_for'. (Es decir, espero que 'enum_for' en sí mismo no sea el problema.) De cualquier manera, me gusta que esto sea simple y legible. Menos sofisticado es a menudo más bueno. – Telemachus

+0

Esto es más rápido porque se ejecuta un bloque para cada carácter en los otros enfoques. Encontré y resolví una pregunta similar en http://stackoverflow.com/questions/6387428/why-is-counting-letters-faster-using-stringcount-than-using-stringchars-in-ruby/6475413#6475413 –

1

Otra solución derivada de la respuesta de FMc:

s = "a#asg#sdfg#d##" 
q = [] 
s.length.times {|i| q << i if s[i,1] == '#'} 

Me encanta que Ruby no sólo tiene una manera de hacer algo!

Cuestiones relacionadas