Cách tốt nhất để cắt một chuỗi thành các đoạn có độ dài nhất định trong Ruby là gì?


88

Tôi đã tìm kiếm một cách thanh lịch và hiệu quả để phân đoạn một chuỗi thành các chuỗi con có độ dài nhất định trong Ruby.

Cho đến nay, điều tốt nhất tôi có thể nghĩ ra là:

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)
=> []

Bạn có thể muốn chunk("", n)quay lại [""]thay vì []. Nếu vậy, chỉ cần thêm dòng này làm dòng đầu tiên của phương thức:

return [""] if string.empty?

Bạn có đề xuất giải pháp nào tốt hơn không?

Biên tập

Cảm ơn Jeremy Ruten về giải pháp thanh lịch và hiệu quả này: [sửa: KHÔNG hiệu quả!]

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

Biên tập

Giải pháp string.scan mất khoảng 60 giây để cắt 512k thành 1k khối 10000 lần, so với giải pháp dựa trên lát cắt ban đầu chỉ mất 2,4 giây.


Giải pháp ban đầu của bạn là hiệu quả và thanh lịch nhất có thể: không cần phải kiểm tra từng ký tự của chuỗi để biết vị trí cần cắt, cũng như không cần biến toàn bộ thành một mảng rồi quay lại lần nữa.
android.weasel

Câu trả lời:


158

Sử dụng 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"]

Ok, bây giờ điều này là tuyệt vời! Tôi biết phải có một cách tốt hơn. Cảm ơn rất nhiều Jeremy Ruten.
MiniQuark

3
def chunk (chuỗi, kích thước); string.scan (/. {1, # {size}} /); kết thúc
MiniQuark

1
Chà, giờ tôi thấy mình thật ngu ngốc. Tôi thậm chí chưa bao giờ bận tâm để kiểm tra cách quét hoạt động.
Chuck

18
Hãy cẩn thận với giải pháp này; đây là một regexp và một /.chút của nó có nghĩa là nó sẽ bao gồm tất cả các ký tự NGOẠI TRỪ các dòng mới \n. Nếu bạn muốn bao gồm dòng mới, sử dụngstring.scan(/.{4}/m)
professormeowingtons

1
Thật là một giải pháp thông minh! Tôi thích regexps nhưng tôi sẽ không sử dụng bộ định lượng cho mục đích này. Cảm ơn bạn Jeremy Ruten
Cec

18

Đây là một cách khác để làm điều đó:

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

=> ["abc", "def", "ghi", "jkl", "mno", "pqr", "stu", "vwx", "yz"]


15
Ngoài ra:"abcdefghijklmnopqrstuvwxyz".chars.each_slice(3).map(&:join)
Finbarr

3
Tôi thích cái này vì nó hoạt động trên các chuỗi có chứa các dòng mới.
Steve Davis

1
Đây nên là giải pháp được chấp nhận. Sử dụng quét có thể làm rơi mã thông báo cuối cùng nếu độ dài không khớp với mẫu .
count0

6

Tôi nghĩ đây là giải pháp hiệu quả nhất nếu bạn biết chuỗi của mình là bội số của kích thước phân đoạn

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

và cho các bộ phận

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

3
Chuỗi của bạn không nhất thiết phải là nhiều kích thước phân đoạn nếu bạn thay thế string.length / sizebằng (string.length + size - 1) / size- mẫu này phổ biến trong mã C phải xử lý việc cắt bớt số nguyên.
nitơ

3

Đây là một giải pháp khác cho trường hợp hơi khác, khi xử lý các chuỗi lớn và không cần phải lưu trữ tất cả các đoạn cùng một lúc. Bằng cách này, nó lưu trữ từng đoạn đơn lẻ và hoạt động nhanh hơn nhiều so với việc cắt các chuỗi:

io = StringIO.new(string)
until io.eof?
  chunk = io.read(chunk_size)
  do_something(chunk)
end

Đối với chuỗi rất lớn, điều này là bởi đến nay các cách tốt nhất để làm điều đó . Điều này sẽ tránh việc đọc toàn bộ chuỗi vào bộ nhớ và nhận được các Errno::EINVALlỗi như Invalid argument @ io_freadInvalid argument @ io_write.
Joshua Pinter

2

Tôi đã thực hiện một thử nghiệm nhỏ để cắt khoảng 593MB dữ liệu thành 18991 mảnh 32KB. Phiên bản bản đồ + lát cắt của bạn đã chạy trong ít nhất 15 phút sử dụng 100% CPU trước khi tôi nhấn ctrl + C. Phiên bản này sử dụng String # unpack đã hoàn thành trong 3,6 giây:

def chunk(string, size)
  string.unpack("a#{size}" * (string.size/size.to_f).ceil)
end

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

Việc loại bỏ là cần thiết vì nó bao gồm khoảng trống giữa các tập hợp. Regex-fu của tôi không khá lắm để biết cách khắc phục điều đó ngay trên đỉnh đầu của tôi.


aproach quét sẽ quên đi các caracte không phù hợp, tức là: nếu bạn thử với một lát chuỗi dài 10 trên 3 phần, bạn sẽ có 3 phần và 1 phần tử sẽ bị loại bỏ, aproach của bạn không làm vậy, vì vậy tốt nhất.
vinicius gati

1

Một giải pháp tốt hơn có tính đến phần cuối cùng của chuỗi có thể nhỏ hơn kích thước đoạn:

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

Bạn có nghĩ đến một số ràng buộc khác không? Nếu không, tôi sẽ rất muốn làm một điều gì đó đơn giản như

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

Tôi thực sự không có bất kỳ ràng buộc nào, ngoài việc có một cái gì đó đơn giản, thanh lịch và hiệu quả. Tôi thích ý tưởng của bạn, nhưng bạn có phiền dịch nó thành một phương pháp không? [0..10] có thể sẽ trở nên phức tạp hơn một chút.
MiniQuark

Tôi đã sửa ví dụ của mình để sử dụng str [i w, w] thay vì str [i w ... (i + 1) * w]. Tx
MiniQuark

Đây phải là (1..10) .collect hơn là [0..10] .each. [1..10] là một mảng bao gồm một phần tử - một dải ô. (1..10) là phạm vi chính nó. Và + each + trả về tập hợp ban đầu mà nó được gọi ([1..10] trong trường hợp này) thay vì các giá trị được khối trả về. Chúng tôi muốn + bản đồ + ở đây.
Chuck

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.