Tenho procurado uma maneira elegante e eficiente de dividir uma string em substrings de um determinado comprimento em Ruby.
Até agora, o melhor que consegui sugerir é o seguinte:
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)
=> []
Você pode querer chunk("", n)
retornar em [""]
vez de []
. Nesse caso, basta adicionar isso como a primeira linha do método:
return [""] if string.empty?
Você recomendaria alguma solução melhor?
Editar
Obrigado a Jeremy Ruten por esta solução elegante e eficiente: [editar: NÃO eficiente!]
def chunk(string, size)
string.scan(/.{1,#{size}}/)
end
Editar
A solução string.scan leva cerca de 60 segundos para dividir 512k em blocos de 1k 10.000 vezes, em comparação com a solução baseada em fatias original, que leva apenas 2,4 segundos.
Respostas:
Use
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"]
fonte
/.
pouco dela significa que incluirá todos os caracteres EXCETO as novas linhas\n
. Se você quiser incluir novas linhas, usestring.scan(/.{4}/m)
Aqui está outra maneira de fazer isso:
"abcdefghijklmnopqrstuvwxyz".chars.to_a.each_slice(3).to_a.map {|s| s.to_s }
=> ["abc", "def", "ghi", "jkl", "mno", "pqr", "stu", "vwx", "yz"]
fonte
"abcdefghijklmnopqrstuvwxyz".chars.each_slice(3).map(&:join)
Eu acho que esta é a solução mais eficiente se você souber que sua string é um múltiplo do tamanho do pedaço
def chunk(string, size) (string.length / size).times.collect { |i| string[i * size, size] } end
e por peças
def parts(string, count) size = string.length / count count.times.collect { |i| string[i * size, size] } end
fonte
string.length / size
por(string.length + size - 1) / size
- esse padrão é comum no código C que precisa lidar com truncamento de inteiro.Aqui está outra solução para casos ligeiramente diferentes, ao processar cadeias de caracteres grandes e não há necessidade de armazenar todos os pedaços de uma vez. Desta forma, ele armazena um único pedaço por vez e tem um desempenho muito mais rápido do que fatiar strings:
io = StringIO.new(string) until io.eof? chunk = io.read(chunk_size) do_something(chunk) end
fonte
Errno::EINVAL
erros comoInvalid argument @ io_fread
eInvalid argument @ io_write
.Fiz um pequeno teste que divide cerca de 593 MB de dados em 18991 pedaços de 32 KB. Sua versão do slice + map rodou por pelo menos 15 minutos usando 100% da CPU antes de eu pressionar ctrl + C. Esta versão usando String # unpack terminou em 3,6 segundos:
def chunk(string, size) string.unpack("a#{size}" * (string.size/size.to_f).ceil) end
fonte
test.split(/(...)/).reject {|v| v.empty?}
A rejeição é necessária porque, de outra forma, inclui o espaço em branco entre os conjuntos. Meu regex-fu não está bem para ver como consertar isso logo de cara.
fonte
Uma solução melhor que leva em conta a última parte da string, que pode ser menor que o tamanho do pedaço:
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
fonte
Existem outras restrições que você tem em mente? Caso contrário, ficaria terrivelmente tentado a fazer algo simples como
[0..10].each { str[(i*w),w] }
fonte
Só
text.scan(/.{1,4}/m)
resolve o problemafonte