著作一覧 |
Rubyで文字列の文字数(バイト数ではなく)を求める方法のまとめ。良い悪いは僕の好み。
ruby -rjcode -Ks -e 'puts "日本語".jsize'
ruby -Ks -e 'puts "日本語".split(//).size'
ruby -Ks -e 'puts "日本語".scan(/./).size'
.
が多い(もっともsplitよりscanのほうが1文字少ないから文字数は同じ)という具合で、共通点をくくりだすと、正規表現の生成はあきらめるしかない(というよりも、シンプルなコードで-Ks, -Ke, -Ku全部を扱えるようにすることを考えると他に手段はないような)。
あとは、もう1つのオブジェクト生成が抑制できれば良さそうだ。
で、まずは
ruby -Ks -e 'i = 0; "日本語".scan(/./) { i += 1 };puts i'
ブロックは作ってるけど必要なメモリー量は最小かも。でもコード量は多い。ということはいちいちこんなものは(2案、3案と違って)書けない。
であれば
class String def char_count cnt = 0 self.scan(/./) { cnt += 1 } cnt end end
追記:/./m にしないと改行を1文字としてカウントしない(それはそうだ)。
なcharstring.rbをrequireさせるようにするのが良さそう。
ちなみに、レシピブックにはこういった「〜するにはどうするの?」が多数収録されているのでRubyを使うなら手元に置いておきたい本です。
Rubyレシピブック 268の技(青木 峰郎)追記:rubycoさんのeach_char。(デフォルト表示のツッコミはリンクにならないので)。
#一瞬、Rubystは文字指向ではなくバイト指向というフレーズを思いついたが、簡単にできるから無いだけだな。
追記:実際に数100Kのテキストで試すとイテレータ版はとても遅い。
で、上のを書いたときも思ったし、成瀬さんのString#each_byteを使う実装を見ても思ったのだけど、injectのデザインパターン(アキュムレータに初期値を与え、要素単位にブロックを適用しその戻り値をアキュムレータに与え、最後にそのアキュムレータの値を返す)をeach以外に適用できないかなと。
で、injectの第1引数に繰り返しメソッドを指定するようにしてみたり。
追記:Enumulatorを使えば良い。
#!/usr/local/bin/ruby -Ks module Enumerable alias :original_inject :inject def inject(*a, &p) if a.size != 2 original_inject(*a, &p) else ac = a[1] self.send(a[0]) {|x| ac = p.call(ac, x) } ac end end end class String def char_count case $KCODE[0] when ?S trail = false inject(:each_byte, 0) { |n, c| if trail trail = false else if c > 0x80 && (c-32)>>6 != 2 trail = true end n += 1 end next n } when ?U inject(:each_byte, 0) { |n, c| if c>>6 != 2 n += 1 end next n } end end end p '日本語'.char_count
末尾がアキュムレータの評価にならない場合があるので最初next
の代わりにreturn
とか書いてはまりにはまった。
でもこれは格好が悪いなぁ(scanの引数をばらけさせてるところ)
module Enumerable def accum(*a) ac = 0 nm = a.shift self.send(nm, *a) {|x| ac += 1 } ac end end p '日本語'.accum(:scan, /./)
ジェズイットを見習え |
http://d.hatena.ne.jp/rubyco/20060318/charcount
配列生成はどーしょもないですけど、正規表現(リテラル)生成は遅くないですよ。埋め込み式がなければ、コード一箇所につきプロセスで一回しかコンパイルされませんから。例外は$KCODEを変えたとき。<br><br>あと、each_byteをつかったやつはたぶんjcode.rbよりも遅いと思います。Rubyレベルのループはめちゃめちゃ遅いので、ちょっとくらいメモリを使ってもCでループさせたほうが速いんです。YARVだとwhileは劇的に高速化しますがイテレータはそれほどでもありません。結局Rubyではコードが短いほど(Cレベルのコードを活用するほど)高速な傾向にあります。<br><br>最後にinjectでeach以外をつかうはなしは、Enumeratorがそのものです。<br><br>% ruby -renumerator -e 'p [5,5,5].to_enum(:each_index).inject(0) {|sum,i| sum + i }'<br>3<br><br>ちなみに Ruby 1.9 だと Enumerator が組み込みになってて、ブロックなしの each や each_index が Enumerator を返すようになってます(ただしExperimental)。
>Enumerator<br>あ、そんなものが。<br>jcode#jsizeは数100K程度だと確かに速かったです。<br>というか、scan(/./)は空白が飛んでしまうという問題があることに気付いた。
違うな。scan版は改行を数えていないのか。だから/./mじゃないとだめですね。
ruby -Ks -e 'puts "日本語".gsub(/./, '@').size'<br>というのもあります。