トップ «前の日記(2006-03-17) 最新 次の日記(2006-03-19)» 編集

日々の破片

Subscribe with livedoor Reader
著作一覧

2006-03-18

_ 多いは少なく少ないは多い

Rubyで文字列の文字数(バイト数ではなく)を求める方法のまとめ。良い悪いは僕の好み。

  1. jcodeを利用 (greenteaさん)
    ruby -rjcode -Ks -e 'puts "日本語".jsize'
    • 良い点:文字列のメソッド
    • 悪い点:正規表現生成、文字列生成とか重そう
  2. split (kdmsnrさんというかレシピブック)
    ruby -Ks -e 'puts "日本語".split(//).size'
    • 良い点:シンプル
    • 悪い点:Arrayの生成、正規表現の生成とか重そう
  3. scan (babieさん)
    ruby -Ks -e 'puts "日本語".scan(/./).size'
    • 良い点:-
    • 悪い点:Arrayの生成、正規表現の生成とか重そうだし、splitよりも.が多い(もっとも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のテキストで試すとイテレータ版はとても遅い

_ Enumerable#injectの拡張

上の続き。 成瀬さんからのトラックバック。Array#injectを使うのは、この場合は「Arrayの生成」が必要なのでリジェクトだけど、String#each_byteを使う例はまさに望み通り(正規表現さえ不要)のやつです。ここの各メソッドを$KCODEの値で呼び出すようにすれば良いわけだし。

で、上のを書いたときも思ったし、成瀬さんの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とか書いてはまりにはまった。

追記:元々は最初のString#scanをそのまま(Arrayを生成せずにinjectデザインパターンを適用。引数のArrayは作ってるじゃんと思うかも知れないけど、でっかなテキストを与えた場合のArray生成が嫌だというのが理由なので、絶対にオブジェクトを作らないということではない)使いたかったのに、違うことをしている。

でもこれは格好が悪いなぁ(scanの引数をばらけさせてるところ)

module Enumerable
  def accum(*a)
    ac = 0
    nm = a.shift
    self.send(nm, *a) {|x|
        ac += 1
    }
    ac
  end
end
p '日本語'.accum(:scan, /./)
本日のツッコミ(全5件) [ツッコミを入れる]
_ rubyco (2006-03-18 06:54)

http://d.hatena.ne.jp/rubyco/20060318/charcount

_ 青木 (2006-03-19 00:32)

配列生成はどーしょもないですけど、正規表現(リテラル)生成は遅くないですよ。埋め込み式がなければ、コード一箇所につきプロセスで一回しかコンパイルされませんから。例外は$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)。

_ arton (2006-03-19 01:48)

>Enumerator<br>あ、そんなものが。<br>jcode#jsizeは数100K程度だと確かに速かったです。<br>というか、scan(/./)は空白が飛んでしまうという問題があることに気付いた。

_ arton (2006-03-19 01:59)

違うな。scan版は改行を数えていないのか。だから/./mじゃないとだめですね。

_ はら (2006-03-20 12:47)

ruby -Ks -e 'puts "日本語".gsub(/./, '@').size'<br>というのもあります。


2003|06|07|08|09|10|11|12|
2004|01|02|03|04|05|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|12|
2007|01|02|03|04|05|06|07|08|09|10|11|12|
2008|01|02|03|04|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|09|10|11|12|
2011|01|02|03|04|05|06|07|08|09|10|11|12|
2012|01|02|03|04|05|06|07|08|09|10|11|12|
2013|01|02|03|04|05|06|07|08|09|10|11|12|
2014|01|02|03|04|05|06|07|08|09|10|11|12|
2015|01|02|03|04|05|06|07|08|09|10|11|12|
2016|01|02|03|04|05|06|07|08|09|10|11|12|
2017|01|02|03|04|05|06|07|08|09|10|11|12|
2018|01|02|03|04|05|06|07|08|09|10|11|12|

ジェズイットを見習え