He estado buscando una forma elegante y eficiente de fragmentar una cadena en subcadenas de una longitud determinada en Ruby.
Hasta ahora, lo mejor que se me ocurrió es esto:
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 chunk("", n)
regresar en [""]
lugar de []
. Si es así, simplemente agregue esto como la primera línea del método:
return [""] if string.empty?
¿Recomendarías alguna solución mejor?
Editar
Gracias a Jeremy Ruten por esta elegante y eficiente solución: [editar: ¡NO eficiente!]
def chunk(string, size)
string.scan(/.{1,#{size}}/)
end
Editar
La solución string.scan tarda unos 60 segundos en cortar 512k en 1k trozos 10000 veces, en comparación con la solución original basada en cortes, que solo tarda 2,4 segundos.
Respuestas:
Utilizar
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"]
fuente
/.
expresión regular , y la parte de ella significa que incluirá todos los caracteres EXCEPTO las líneas nuevas\n
. Si desea incluir nuevas líneas, usestring.scan(/.{4}/m)
Aquí hay otra forma 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"]
fuente
"abcdefghijklmnopqrstuvwxyz".chars.each_slice(3).map(&:join)
Creo que esta es la solución más eficiente 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 para partes
def parts(string, count) size = string.length / count count.times.collect { |i| string[i * size, size] } end
fuente
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.Aquí hay otra solución para un caso ligeramente diferente, cuando se procesan cadenas grandes y no es necesario almacenar todos los fragmentos a la vez. De esta manera, almacena un solo fragmento a la vez y funciona mucho más rápido que cortar cadenas:
io = StringIO.new(string) until io.eof? chunk = io.read(chunk_size) do_something(chunk) end
fuente
Errno::EINVAL
errores comoInvalid argument @ io_fread
yInvalid argument @ io_write
.Hice una pequeña prueba que corta aproximadamente 593 MB de datos en 18991 piezas de 32 KB. Su versión de slice + map se ejecutó durante al menos 15 minutos usando el 100% de CPU antes de presionar ctrl + C. Esta versión que usa String # unpack terminó en 3.6 segundos:
def chunk(string, size) string.unpack("a#{size}" * (string.size/size.to_f).ceil) end
fuente
test.split(/(...)/).reject {|v| v.empty?}
El rechazo es necesario porque, de lo contrario, incluye el espacio en blanco entre conjuntos. Mi regex-fu no está a la altura de ver cómo arreglar eso de la parte superior de mi cabeza.
fuente
Una mejor solución que tiene en cuenta la última parte de la cadena, que podría ser menor que el tamaño del fragmento:
def chunk(inStr, sz) return [inStr] if inStr.length < sz m = inStr.length % sz # this is the last part of the string partial = (inStr.length / sz).times.collect { |i| inStr[i * sz, sz] } partial << inStr[-m..-1] if (m % sz != 0) # add the last part partial end
fuente
¿Tiene otras limitaciones en mente? De lo contrario, estaría terriblemente tentado a hacer algo simple como
[0..10].each { str[(i*w),w] }
fuente
Solo
text.scan(/.{1,4}/m)
resuelve el problemafuente