views:

477

answers:

3

I have been looking for an elegant and efficient way to chunk a string into substrings of a given length in Ruby.

So far, the best I could come up with is this:

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

You might want chunk("", n) to return [""] instead of []. If so, just add this as the first line of the method:

return [""] if string.empty?

Would you recommend any better solution?

+1  A: 

Are there some other constraints you have in mind? Otherwise I'd be awfully tempted to do something simple like

[0..10].each {
   str[(i*w),w]
}
Charlie Martin
I don't really have any constraint, apart from having something simple, elegant and efficient. I like your idea, but would you mind translating it into a method please? The [0..10] would probably become slightly more complex.
MiniQuark
I fixed my example to use str[i*w,w] instead of str[i*w...(i+1)*w]. Tx
MiniQuark
This should be (1..10).collect rather than [0..10].each. [1..10] is an array consisting of one element -- a range. (1..10) is the range itself. And +each+ returns the original collection that it's called on ([1..10] in this case) rather than the values returned by the block. We want +map+ here.
Chuck
A: 
test.split(/(...)/).reject {|v| v.empty?}

The reject is necessary because it otherwise includes the blank space between sets. My regex-fu isn't quite up to seeing how to fix that right off the top of my head.

Chuck
+12  A: 

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"]
yjerem
Ok, now this is excellent! I knew there had to be a better way. Thanks a lot Jeremy Ruten.
MiniQuark
def chunk(string, size); string.scan(/.{1,#{size}}/); end
MiniQuark
Wow, I feel stupid now. I've never even bothered to check how scan worked.
Chuck