2009-04-16 9 views
73

He estado buscando una manera elegante y eficiente de dividir una cadena en subcadenas de una longitud determinada en Ruby.¿Cuál es la mejor manera de cortar una cuerda en trozos de una longitud determinada en Ruby?

Hasta ahora, lo mejor que podía llegar a es la siguiente:

def chunk(string, size) 
    (0..(string.length-1)/size).map{|i|string[i*size,size]} 
end 

>> chunk("abcdef",3) 
=> ["abc", "def"] 
>> chunk("abcde",3) 
=> ["abc", "de"] 
>> chunk("abc",3) 
=> ["abc"] 
>> chunk("ab",3) 
=> ["ab"] 
>> chunk("",3) 
=> [] 

Es posible que desee volver a chunk("", n)[""] en lugar de []. Si es así, simplemente agregue esto como la primera línea del método:

return [""] if string.empty? 

¿Recomendaría alguna solución mejor?

Editar

Gracias a Jeremy Ruten para esta solución elegante y eficaz:

def chunk(string, size) 
    string.scan(/.{1,#{size}}/) 
end 

Respuesta

132

Uso String#scan:

>> 'abcdefghijklmnopqrstuvwxyz'.scan(/.{4}/) 
=> ["abcd", "efgh", "ijkl", "mnop", "qrst", "uvwx"] 
>> 'abcdefghijklmnopqrstuvwxyz'.scan(/.{1,4}/) 
=> ["abcd", "efgh", "ijkl", "mnop", "qrst", "uvwx", "yz"] 
>> 'abcdefghijklmnopqrstuvwxyz'.scan(/.{1,3}/) 
=> ["abc", "def", "ghi", "jkl", "mno", "pqr", "stu", "vwx", "yz"] 
+0

Ok, ahora esto es excelente! Sabía que tenía que haber una mejor manera. Muchas gracias Jeremy Ruten. – MiniQuark

+1

def chunk (cadena, tamaño); string.scan (/. {1, # {size}} /); fin – MiniQuark

+1

Guau, me siento estúpido ahora. Nunca me había molestado en comprobar cómo funcionaba el escaneo. – Chuck

0

¿Hay algunas otras limitaciones que tiene en mente? De lo contrario estaría muy tentado a hacer algo tan simple como

[0..10].each { 
    str[(i*w),w] 
} 
+0

Realmente no tengo ningún tipo de restricción, aparte de tener algo simple, elegante y eficiente. Me gusta tu idea, pero ¿te importaría traducirla en un método, por favor? El [0..10] probablemente se volvería un poco más complejo. – MiniQuark

+0

He arreglado mi ejemplo para usar str [i * w, w] en lugar de str [i * w ... (i + 1) * w]. Tx – MiniQuark

+0

Esto debería ser (1..10) .collect en lugar de [0..10] .each. [1..10] es una matriz que consta de un elemento: un rango. (1..10) es el rango en sí. Y + cada + devuelve la colección original a la que se llama (en este caso, [1..10]) en lugar de los valores devueltos por el bloque. Queremos + mapa + aquí. – Chuck

1
test.split(/(...)/).reject {|v| v.empty?} 

El rechazo es necesario, ya que de lo contrario incluye el espacio en blanco entre las series. Mi regex-fu no está a la altura de ver cómo arreglar eso directamente de mi cabeza.

+0

el enfoque de escaneo se olvidará de los caracteres no coincidentes, es decir: si lo intenta con un segmento de 10 longitudes de cadena en 3 partes, tendrá 3 partes y se descartará 1 elemento, su enfoque no lo hará, por lo que es mejor. –

15

Aquí hay otra manera de hacerlo:

"abcdefghijklmnopqrstuvwxyz".chars.to_a.each_slice(3).to_a.map {|s| s.to_s } 

=> [ "abc", "def", "ghi", "jkl", "MNO ", 'PQR', 'Stu', 'VWX', 'yz']

+11

Alternativamente: '" abcdefghijklmnopqrstuvwxyz ".chars.each_slice (3) .map (&: join)' – Finbarr

+3

Me gusta esta porque funciona en cadenas que contienen líneas nuevas. –

+1

Esta debería ser la solución aceptada. El uso del escaneo puede soltar el último token si la longitud no coincide con _pattern_. – count0

2

Creo que esta es la solución más eficaz si sabe que su cadena es un múltiplo del tamaño del fragmento

def chunk(string, size) 
    (string.length/size).times.collect { |i| string[i * size, size] } 
end 

y de piezas

def parts(string, count) 
    size = string.length/count 
    count.times.collect { |i| string[i * size, size] } 
end 
+1

Su cadena no tiene que ser un múltiplo del tamaño del fragmento si reemplaza 'string.length/size' con' (string.length + size - 1)/size' - este patrón es común en el código C que tiene que lidiar con el truncamiento de enteros. – nitrogen

Cuestiones relacionadas