在Ruby中将字符串切成给定长度的块的最佳方法是什么?


87

我一直在寻找一种优雅而有效的方法来在Ruby中将字符串分块为给定长度的子字符串。

到目前为止,我能想到的最好的方法是:

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

您可能要chunk("", n)返回[""]而不是[]。如果是这样,只需将其添加为方法的第一行即可:

return [""] if string.empty?

您会提出更好的解决方案吗?

编辑

感谢Jeremy Ruten提供的这种优雅而有效的解决方案:[编辑:效率不高!]

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

编辑

string.scan解决方案大约需要60秒才能将512k砍成1k块10000次,而原始的基于切片的解决方案只需要2.4秒。


您最初的解决方案是尽可能地高效和优雅:无需检查字符串的每个字符就知道在哪里进行切分,也无需将整个内容转换为数组然后再次返回。
android.weasel19年

Answers:


158

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

好的,这太好了!我知道必须有更好的方法。非常感谢Jeremy Ruten。
MiniQuark

3
def块(字符串,大小); string.scan(/。{1,#{size}} /); 结束
MiniQuark

1
哇,我现在很蠢。我什至从未费心检查扫描的工作方式。
Chuck

18
请谨慎使用此解决方案;这是一个正则表达式,/.它的位意味着它将包括除换行符之外的所有字符\n。如果您想包括换行符,请使用string.scan(/.{4}/m)
Professormeowingtons

1
多么聪明的解决方案!我喜欢正则表达式,但是我不会为此使用量词。谢谢杰里米·鲁滕
Cec

18

这是另一种方法:

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

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


15
或者:"abcdefghijklmnopqrstuvwxyz".chars.each_slice(3).map(&:join)
Finbarr 2012年

3
我喜欢这一行,因为它适用于包含换行符的字符串。
史蒂夫·戴维斯

1
这应该是公认的解决方案。如果长度不匹配pattern,使用scan可能会丢弃最后一个令牌。
2016年

6

如果您知道字符串是块大小的倍数,我认为这是最有效的解决方案

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

和零件

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

3
如果用替换string.length / size,则字符串不必是块大小的倍数(string.length + size - 1) / size-这种模式在必须处理整数截断的C代码中很常见。
氮气

3

对于处理稍有不同的情况,这是另一种解决方案,当处理大字符串时,无需一次存储所有块。这样,它一次存储单个块,并且比切片字符串快得多:

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

对于非常大的字符串,这是迄今为止做最好的方式。这样可以避免将整个字符串读到内存中,并避免Errno::EINVAL出现类似Invalid argument @ io_fread和的错误Invalid argument @ io_write
约书亚·品特

2

我做了一点测试,将约593MB的数据切成18991个32KB的数据。在按ctrl + C之前,您的slice + map版本使用100%CPU运行了至少15分钟。使用String#unpack的该版本在3.6秒内完成:

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

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

拒绝是必需的,因为否则会在组之间包含空白。我的regex-fu还不完全了解如何解决这个问题。


扫描方法会忘记不匹配的特征,即:如果您尝试将长度为10的字符串切片分为3个部分,则您将分为3个部分,并且将删除1个元素,则您的方法将不这样做,因此是最好的。
vinicius gati 2014年

1

一个更好的解决方案,它考虑了字符串的最后一部分,该部分可能小于块大小:

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

您还有其他限制吗?否则我会很想做一些简单的事情,例如

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

除了简单,优雅和高效之外,我没有任何约束。我喜欢您的想法,但是您介意将其转换为方法吗?[0..10]可能会变得稍微复杂一些。
MiniQuark

我将示例固定为使用str [i w,w]代替str [i w ...(i + 1)* w]。Tx
MiniQuark

这应该是(1..10).collect而不是[0..10] .each。[1..10]是由一个元素(一个范围)组成的数组。(1..10)是范围本身。并且+ each +返回调用它的原始集合(在这种情况下为[1..10]),而不是该块返回的值。我们要在这里+ map +。
Chuck
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.