| 著作一覧 |
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>というのもあります。