Saya telah mencari cara yang elegan dan efisien untuk memotong string menjadi substring dengan panjang tertentu di Ruby.
Sejauh ini, yang terbaik yang bisa saya dapatkan adalah ini:
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)
=> []
Anda mungkin ingin chunk("", n)
kembali, [""]
bukan []
. Jika demikian, tambahkan saja ini sebagai baris pertama dari metode ini:
return [""] if string.empty?
Apakah Anda akan merekomendasikan solusi yang lebih baik?
Sunting
Terima kasih kepada Jeremy Ruten untuk solusi yang elegan dan efisien ini: [edit: TIDAK efisien!]
def chunk(string, size)
string.scan(/.{1,#{size}}/)
end
Sunting
Solusi string.scan membutuhkan waktu sekitar 60 detik untuk memotong 512k menjadi 1k potongan 10000 kali, dibandingkan dengan solusi berbasis slice asli yang hanya membutuhkan 2,4 detik.
Jawaban:
Penggunaan
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"]
sumber
/.
sedikit artinya itu akan menyertakan semua karakter KECUALI baris baru\n
. Jika Anda ingin memasukkan baris baru, gunakanstring.scan(/.{4}/m)
Berikut cara lain untuk melakukannya:
"abcdefghijklmnopqrstuvwxyz".chars.to_a.each_slice(3).to_a.map {|s| s.to_s }
=> ["abc", "def", "ghi", "jkl", "mno", "pqr", "stu", "vwx", "yz"]
sumber
"abcdefghijklmnopqrstuvwxyz".chars.each_slice(3).map(&:join)
Saya pikir ini adalah solusi paling efisien jika Anda tahu string Anda adalah kelipatan ukuran chunk
def chunk(string, size) (string.length / size).times.collect { |i| string[i * size, size] } end
dan untuk suku cadang
def parts(string, count) size = string.length / count count.times.collect { |i| string[i * size, size] } end
sumber
string.length / size
dengan(string.length + size - 1) / size
- pola ini umum terjadi pada kode C yang harus berurusan dengan pemotongan integer.Berikut adalah satu solusi lain untuk kasus yang sedikit berbeda, saat memproses string besar dan tidak perlu menyimpan semua potongan sekaligus. Dengan cara ini, ia menyimpan potongan tunggal pada satu waktu dan bekerja lebih cepat daripada mengiris string:
io = StringIO.new(string) until io.eof? chunk = io.read(chunk_size) do_something(chunk) end
sumber
Errno::EINVAL
kesalahan sepertiInvalid argument @ io_fread
danInvalid argument @ io_write
.Saya membuat tes kecil yang memotong sekitar 593MB data menjadi 18991 32KB potongan. Versi slice + map Anda berjalan setidaknya selama 15 menit menggunakan CPU 100% sebelum saya menekan ctrl + C. Versi ini menggunakan String # membongkar selesai dalam 3,6 detik:
def chunk(string, size) string.unpack("a#{size}" * (string.size/size.to_f).ceil) end
sumber
test.split(/(...)/).reject {|v| v.empty?}
Penolakan diperlukan karena jika tidak termasuk ruang kosong di antara set. Regex-fu saya tidak cukup untuk melihat bagaimana cara memperbaikinya langsung dari pikiran saya.
sumber
Solusi yang lebih baik yang memperhitungkan bagian terakhir dari string yang bisa lebih kecil dari ukuran potongan:
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
sumber
Apakah ada kendala lain yang Anda pikirkan? Jika tidak, saya akan tergoda untuk melakukan sesuatu yang sederhana seperti
[0..10].each { str[(i*w),w] }
sumber
Hanya
text.scan(/.{1,4}/m)
menyelesaikan masalahsumber