97

我一直在寻找一种优雅而有效的方法来将字符串分块为 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 解决方案将 512k 切成 1k 块 10000 次大约需要 60 秒,而原始基于切片的解决方案仅需要 2.4 秒。

4

9 回答 9

168

使用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"]
于 2009-04-16T01:26:05.413 回答
21

这是另一种方法:

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

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

于 2011-02-04T20:04:59.770 回答
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
于 2015-07-26T14:00:08.720 回答
5

我做了一个小测试,将大约 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
于 2020-02-20T23:17:31.453 回答
4

这是另一种解决方案略有不同的情况,当处理大字符串并且不需要一次存储所有块时。通过这种方式,它一次存储单个块并且执行速度比切片字符串快得多:

io = StringIO.new(string)
until io.eof?
  chunk = io.read(chunk_size)
  do_something(chunk)
end
于 2018-09-20T09:28:42.927 回答
1
test.split(/(...)/).reject {|v| v.empty?}

拒绝是必要的,因为它包括集合之间的空白空间。我的 regex-fu 还没有完全想到如何解决这个问题。

于 2009-04-16T01:20:56.657 回答
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
于 2019-02-06T10:19:11.333 回答
0

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

[0..10].each {
   str[(i*w),w]
}
于 2009-04-16T01:15:04.520 回答
0

只是text.scan(/.{1,4}/m)解决问题

于 2020-11-08T11:56:59.657 回答