Rubyで文字数を指定して文字列を分割したいって思ったのでちょっと調べたらいろいろ勉強になったので、そのメモです。
余談ですが、どうしてそんなことしようとしたのかというと、TwitterBotに投稿させるとき、内容が140字オーバーしてたら分割して投稿するようにしたかったので。
調べたら答えは出てきたのですが、(参考→Ruby 文字列を任意の文字数に分割する)
s = "1234567890"
n = 3
s.scan(/.{1,#{n}}/)
# => ["123", "456", "789", "0"]
パっと見理解できなかったので、ひとつひとつ見ていきます。
scanメソッド
まず、scanメソッドというのは、
「引数で指定した正規表現のパターンとマッチする部分を文字列からすべて取り出し、配列にして返すメソッド」
だそうです。→str.scan(pattern)より。
つまり、条件に合うやつがひとつひとつ配列の要素になる的な。
任意の文字にマッチさせる
ではその条件というのはどう指定すればいいのでしょうか。
自分は「指定した文字数で分割していって一番最後の要素はそれ以下でもよい」って感じにしたいです。
Rubyがサポートしてる正規表現のメタ文字において、"."は「改行を除く任意の1文字」にマッチします。しかし、Twitterの投稿において改行(\n)も1文字として数えられるので改行にもマッチしてほしい…。
そのためには正規表現に対してオプションを指定すればよさそうです。
p "\n".match(/./) # => nil
p "\n".match(/./m) # => <MatchData "\n">
"/"の直後の文字によってオプションを指定でき、"m"は複数行モードでその能力は
「正規表現 "." が改行にもマッチするようになるッ!!」です。
繰り返しで分割文字数指定
あとは"."がどれくらい繰り返されるかを指定すれば分割できそうですね。
範囲指定繰り返し制御は"{}"を使います。
p "aaaa".match(/.{1}/) # => <MatchData "a"> 1回
p "aaaa".match(/.{1,}/) # => <MatchData "aaaa"> 1回以上
p "aaaa".match(/.{1,3}/) # => <MatchData "aaa"> 1回以上最大3回
140字以内で1文字以上としたいので"{1,140}"とすればよさそうです。
終わりに
post_text.scan(/.{1,140}/m)
とすれば「改行を含む140字以上の文字列を、140字ずつ分割して配列の要素にし、最後の要素は140字以下のものでもよい」って感じになるわけですね。
正規表現って難しい…。
追記(2014/09/19)
コメントしていただいた方法を各段階ごとで出力してみました。
文字列を取り出す→指定した数の要素を取り出してまとめる→まとめたものをjoinして配列に、という流れだそうです。
post_text = "abc\ndefg\nhigkl"
post_text.each_char{|s| p s}
post_text.each_char.each_slice(3){|s| p s}
p post_text.each_char.each_slice(3).map(&:join)
これを実行してみると…
$ ruby test.rb
"a"
"b"
"c"
"\n"
"d"
"e"
"f"
"g"
"\n"
"h"
"i"
"g"
"k"
"l"
["a", "b", "c"]
["\n", "d", "e"]
["f", "g", "\n"]
["h", "i", "g"]
["k", "l"]
["abc", "\nde", "fg\n", "hig", "kl"]
なるほどなあという感じでした。
改行が含まれてても大丈夫だし、こっちの方法もよさそうですね。