Apa cara terbaik untuk memotong string menjadi potongan dengan panjang tertentu di Ruby?


89

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.


Solusi asli Anda seefisien dan seelegan mungkin: tidak perlu memeriksa setiap karakter string untuk mengetahui di mana harus memotongnya, atau perlu mengubah semuanya menjadi array dan kemudian kembali lagi.
android.weasel

Jawaban:


159

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"]

Oke, sekarang ini luar biasa! Saya tahu pasti ada cara yang lebih baik. Terima kasih banyak, Jeremy Ruten.
MiniQuark

3
def chunk (string, size); string.scan (/. {1, # {size}} /); akhir
MiniQuark

1
Wow, saya merasa bodoh sekarang. Saya tidak pernah repot-repot memeriksa cara kerja pemindaian.
Chuck

18
Hati-hati dengan solusi ini; ini adalah regexp, dan /.sedikit artinya itu akan menyertakan semua karakter KECUALI baris baru \n. Jika Anda ingin memasukkan baris baru, gunakanstring.scan(/.{4}/m)
professormeowingtons

1
Solusi yang sangat cerdas! Saya suka regexps tetapi saya tidak akan pernah menggunakan pembilang untuk tujuan ini. Terima kasih Jeremy Ruten
Cec

18

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"]


16
Atau:"abcdefghijklmnopqrstuvwxyz".chars.each_slice(3).map(&:join)
Finbarr

3
Saya suka yang ini karena berfungsi pada string yang berisi baris baru.
Steve Davis

1
Ini harus menjadi solusi yang diterima. Menggunakan scan mungkin menjatuhkan token terakhir jika panjangnya tidak cocok dengan pola .
hitung0

6

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

3
String Anda tidak harus berupa kelipatan ukuran potongan jika Anda mengganti string.length / sizedengan (string.length + size - 1) / size- pola ini umum terjadi pada kode C yang harus berurusan dengan pemotongan integer.
nitrogen

3

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

Untuk string yang sangat besar, ini adalah jauh yang cara terbaik untuk melakukannya . Ini akan menghindari pembacaan seluruh string ke dalam memori dan mendapatkan Errno::EINVALkesalahan seperti Invalid argument @ io_freaddan Invalid argument @ io_write.
Joshua Pinter

2

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

1
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.


pendekatan pemindaian akan melupakan karakter yang tidak cocok, yaitu: jika Anda mencoba dengan potongan string 10 panjang pada 3 bagian, Anda akan memiliki 3 bagian dan 1 elemen akan dijatuhkan, pendekatan Anda tidak melakukannya, jadi yang terbaik.
vinicius gati

1

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

0

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]
}

Saya tidak memiliki kendala apa pun, selain memiliki sesuatu yang sederhana, elegan, dan efisien. Saya suka ide Anda, tetapi bisakah Anda menerjemahkannya ke dalam metode? [0..10] mungkin akan menjadi sedikit lebih kompleks.
MiniQuark

Saya memperbaiki contoh saya untuk menggunakan str [i w, w] daripada str [i w ... (i + 1) * w]. Tx
MiniQuark

Ini harus (1..10) .collect daripada [0..10] .each. [1..10] adalah larik yang terdiri dari satu elemen - rentang. (1..10) adalah kisaran itu sendiri. Dan + setiap + mengembalikan koleksi asli yang dipanggil ([1..10] dalam hal ini) daripada nilai yang dikembalikan oleh blok. Kami ingin + peta + di sini.
Chuck

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.