単項マイナスと構文解析
単項マイナスとは 単項マイナスと括弧 括弧なし単項マイナスを許容する場合のBNF calcの場合
今回はシンボルとハッシュについて説明します。
Rubyのドキュメントには、シンボルについて次のように書かれています。
Rubyの内部実装では、メソッド名や変数名、定数名、クラス名などの`名前’を整数で管理しています。 これは名前を直接文字列として処理するよりも速度面で有利だからです。 そしてその整数をRubyのコード上で表現したものがシンボルです。
シンボルは、ソース上では文字列のように見え、内部では整数として扱われる、両者を仲立ちするような存在です。
この説明を理解するには、一般にプログラム言語が変数などをどのように管理しているかを、おおまかにでも理解する必要があります。
ここでは非常に簡単なプログラム言語を考え、変数をどう扱えば良いかを考えてみます。 この言語は名前を「mini」ということにします。
この言語のプログラムは例えば次のようなものです。
abc = 100
efg = 50
print abc
print efg
abc = 200
print abc
このプログラムを実行すると
100
50
200
と表示されます。
では、miniは変数をどのように扱うのでしょうか? プログラムの各行に対するminiの動作は次のようになります。
このことから、miniには変数名とその値を記録する表(table)が必要です。 この表を「シンボル・テーブル」ともいいます。
Rubyでminiを書いてみましょう。 シンボルテーブルは二重配列で表すことにします。 上の例では最終的にシンボルテーブルは
[ [ "abc", 200], [ "efg", 50 ] ]
となります。 miniのプログラムは次のようになります。
@sym_table = []
def lookup(k)
@sym_table.each do |a|
if a[0] == k
return a[1]
end
end
nil
end
def install (k, v)
@sym_table.each do |a|
if a[0] == k
a[1] = v
return
end
end
@sym_table << [k, v]
end
File.readlines(ARGV[0]).each_with_index do |s, i|
if /^print +(\w+)$/ =~ s
k = $1
v = lookup(k)
if v
print v, "\n"
else
print "Error #{k} is not defined.\n"
exit
end
elsif /(\w+) *= *(\d+)$/ =~ s
k = $1
v = $2
install(k, v)
elsif /^ *$/ =~ s
# ignore
else
print "Syntax error in #{i+1}\n"
exit
end
end
@のつく変数がここではじめて出てきました。
この変数は「インスタンス変数」といいます。
インスタンス変数はメソッド定義の外でも内でも参照することができます。
インスタンス変数の説明はクラス定義のところでします。
@sym_table
がシンボルテーブルです。
each_with_index
のイテレーションでは、まず最初の引数を取り出し、その名前のファイルを行ごとに読み込んで配列にする。
その各行に対して、代入、print、空行、その他に対してそれぞれ必要なことを行う#
以下はコメントとして実行せず、無視します)このプログラムを動かしてみましょう。 最初に示したminiのプログラムを「mini_sample.txt」という名前で保存しておきます。
$ ruby mini.rb mini_sample.txt
100
50
200
$
期待通りに動作しました。
さて、このプログラムは動作しますが、プログラムが長くなり、変数が多くなると時間がかかるようになります。 ですから、シンボルテーブルへの登録と参照をもっと高速にするアルゴリズムが必要ですが、ここでその説明をすると長くなるので詳しいことは省略します。
さて、mini言語の例ででてきた変数名と配列のインデックス(整数)は1対1に対応しています。
abc
のインデックスは0efg
のインデックスは1これから文字列(変数名)の代わりに整数(インデックス)を使っても良いわけです。 この整数をRubyではシンボルといいます。
ただ、Rubyの実装は上記の例とは全然違うので、例え話と考えてください。 いずれにしても「文字列と整数が1対1に対応する仕組みがあるので、文字列の代わりに整数を使っても良い」というのがシンボルのアイディアです。
シンボルの良いところは、あるシンボルと別のシンボルが同じかどうかはその整数値がイコールかどうかで判断できる、したがって「比較(イコールの判定)を高速にできる」ということです。 このように、シンボルを文字列の代わりに使うと良い面がありますが、逆もあります。 例えばシンボルの文字列を「小文字から大文字に変換する」ためには、いったんシンボルを文字列に直してからその操作を行い、得られた文字列を再びシンボルに直すので、余計な時間がかかることになります。 この場合は文字列のままで操作をするほうが合理的です。
では、シンボルを使うと良いのはどのような場面でしょうか? Rubyのドキュメントには次のように書かれています。
実用面では、シンボルは文字の意味を明確にします。「名前」を指し示す時など、文字列そのものが必要なわけではない時に用います。
- ハッシュのキー { :key => “value” }
- アクセサの引数で渡すインスタンス変数名 attr_reader :name
- メソッド引数で渡すメソッド名 __send__ :to_s
- C の enum 的な使用 (値そのものは無視してよい場合)
シンボルを使うメリットは
- 新しく文字列を生成しない分やや効率がよく、比較も高速。
- 文字の意味がはっきりするのでコードが読みやすくなる
- immutable なので内容を書き換えられる心配がない
これを現時点で完全に理解するのは難しいと思います。 一番用いられるのは、次の項で説明するハッシュのキーとしてなので、それだけでも覚えておけば十分だと思います。 通常は文字列を使っていれば良いと思います。
さて、シンボルのリテラルは3通りあります。
:abc
:'abc'
%s!abc!
この3つはいずれも文字列abcに対応するシンボルを表します。 最初の書き方が一番使われますが、すべての文字が使えるわけではありません。 アルファベットや数字は大丈夫ですが区切り文字の一部は使えません。 詳しくはRubyのドキュメントで確認してください。
シンボルは文字列オブジェクトと違い、同じ文字列のシンボルはオブジェクトとしても同じです。
"abc" == "abc"
はtrue。==
は異なるオブジェクトでも文字列が同じならばtrueだから"abc".equal?("abc")
はfalse。equal?
メソッドは同じオブジェクトでなければfalseになるから。
最初の”abc”で文字列オブジェクトが作られ、2番目の”abc”で別の文字列オブジェクトが作られるから。:abc == :abc
はtrue。==
は同じシンボルならばtrueになる(この「同じ」というのはオブジェクトとして同じと考えても、文字列として同じと考えてもよい):abc.equal?(:abc)
はtrue。同じ文字列のシンボルはひとつしかないから。
最初の:abcでシンボルオブジェクトが作られ、2番目の:abcでは同じオブジェクトが参照されている。最後に注意を述べます。 「シンボルは文字列を整数で表したもの」といいましたが、シンボル・オブジェクトは整数オブジェクトではありません。 したがって、整数のメソッドのabsなどはシンボルには使えません。 また、シンボルを整数に直すメソッドもありません。 あくまでシンボルは「文字列を表すもの」です。
ハッシュは配列に似ていますが、インデックスに代わって任意のオブジェクトを用いることができます。
a = [ 10, 100, 1000 ]
h = { "ten" => 10, 100 => 100, nil => 1000 }
print a[0], "\n"
print a[1], "\n"
print a[2], "\n"
print h["ten"], "\n"
print h[100], "\n"
print h[nil], "\n"
実行すると
10
100
1000
10
100
1000
となります。
配列ではインデックスが0から始まる数字で、0番目の要素はa[0]
で取り出せました。
ハッシュの場合は任意のオブジェクトに対して値が対応します。
上の例では文字列”ten”に対して整数10が対応するので、h["ten"]
で10が取り出せます。
ハッシュのことを連想配列ということもあります。 ハッシュは波カッコで囲み、各要素をコンマで区切ります。 各要素は、「キー」=>「値」というパターンで記述します。
キーとなるオブジェクトにはhash
とequ?
の2つのメソッドが定義されていなければなりません。
Rubyのドキュメントを見ると、Stringクラス(文字列オブジェクトのクラス)にはhashメソッドがありますが、Integerクラス(整数オブジェクトのクラス)にはhashメソッドがありません。
しかし、IntegerクラスにはRubyの組み込みのhash関数が適用されるので、実はhash関数が備わっています。
hashはキーになるオブジェクトからhashメソッドを使い、そのハッシュ値(整数値)を求めて保持して、
a["tex"]
を実行する時に、”tex”のhash値とそれを比較して、対応する値を返しています(おそらく)。
キーであるオブジェクトが変更可能で、変更してしまうと、値を取り出せなくなってしまいます。
(そのときはrehashするという方法がありますが)。
ですから、キーは変更不可能なオブジェクトが適しています。
なお、文字列をキーにした場合、「文字列は変更可能」で「キーは変更不可能がのぞましい」という相反する条件を解決するために「ハッシュはキー文字列をコピーし、その複製をフリーズ」します(変更不可にする)。
元の文字列は変更可能のままです。
ハッシュのキーは文字列よりもシンボルの方が適しています。
これより、キーをシンボルで書けるならば、そうすることが望ましいです。
h = { :ten => 10, :hundred => 100, :thousand => 1000 }
この書き方の簡略な方法として、コロンを文字列の右に書くことができます。
h = { ten: 10, hundred: 00, thousand: 000 }
矢印を省略でいるのでタイプ量も減り、効率的です。
ハッシュはユーザ管理などに使えます。 ひとりひとりのユーザをハッシュで表し、
a = {name: "山田 太郎", email: "taro@example.co.jp" }
b = {name: "鈴木 花子", email: "hanako@example.com" }
このようにハッシュにすると分かりやすくなります。 同じデータを配列にした場合、どうなるかを見てみましょう。
a = [ "山田 太郎", "taro@example.co.jp" ]
b = [ "鈴木 花子", "hanako@example.com" ]
変数aのユーザ名を表すには、
a[:name]
a[0]
明らかにハッシュのほうが分かりやすいです。 項目が増えれば増えるほどハッシュの方に軍配があがります。
このセクションのはじめにシンボルテーブルを説明しました。 プログラムminiでは配列を使ってシンボルテーブルを実装しましたが、ハッシュを使うと非常に簡単になります。 そして、lookupとinstallメソッドはハッシュの参照と代入でカバーすることができます。 以下にそのプログラムを示します。
@sym_table = {}
File.readlines(ARGV[0]).each_with_index do |s, i|
# print @sym_table, "\n"
if /^print +(\w+)$/ =~ s
k = $1
v = @sym_table[k]
if v
print v, "\n"
else
print "Error #{k} is not defined.\n"
exit
end
elsif /(\w+) *= *(\d+)$/ =~ s
k = $1
v = $2
@sym_table[k] = v
elsif /^ *$/ =~ s
# ignore
else
print "Syntax error in #{i+1}\n"
exit
end
end
ハッシュのキーは文字列を使っていますが、to_sym
メソッドでシンボルにすることも考えられます。
シンボルテーブル@sym_table
が大きくなる可能性がある場合は、シンボルに直したほうが高速になると思います。
いずれにしても、ハッシュを使うことでシンボルテーブルは非常に単純になります。
次に大量のデータを処理するシンボルテーブルの例を紹介します。
シンボルテーブルを配列で作り、いちいち頭から検索すると時間がかかります。 これは大量のデータを処理するときには深刻な問題になります。 一般にはこの問題を解決するために、シンボルテーブルにハッシュ関数を使います。 しかし、Rubyにはシンボルがあるので、シンボルをキーにしたハッシュをシンボルテーブルに用いれば単純で高速なプログラムを作ることができます。
その実験として、「不思議の国のアリス」の単語の頻度を調べるプログラムを作ってみました。 ルイス・キャロルの書いた「不思議の国のアリス」(Alice’s Adventures in Wonderland)は1865年刊行ですでに著作権が切れています。 そして、「プロジェクト・グーテンベルク」というウェブサイトにその電子版が掲載され、無料でダウンロードできます。 そこからプレーンテキスト版「pg28885.txt」をダウンロードして使います。 単語は、アルファベットのみからなるので、文字列メソッドのscanを使いました。
@a = File.read("_example/pg28885.txt").scan(/[[:word:]]+/)
/[[:word:]]+/
は正規表現で、単語を構成する文字(英数字)が1つ以上続くものを表すこれで単語の配列が得られます。 以下のプログラムでは配列を頭から検索する方法(wc1)と、シンボルをキーとしたハッシュを使う方法(wc2)を行い、 ベンチマーク・オブジェクトを使って時間計測を行いました。
require 'benchmark'
@a = File.read("_example/pg28885.txt").scan(/[[:word:]]+/)
# 配列による単語の頻度調査
def install_a (w)
@word_table_a.each do |a|
if a[0] == w
a[1] += 1
return
end
end
@word_table_a << [w, 1]
end
def wc1
@word_table_a = []
@a.each { |s| install_a(s) }
@word_table_a.sort{|a,b| a[1] <=> b[1]}.reverse.take(10)
end
# ハッシュによる単語の頻度調査
def install_s (w)
s = w.to_sym
if @word_table_s.has_key?(s)
@word_table_s[s] += 1
else
@word_table_s[s] = 1
end
end
def wc2
@word_table_s = {}
@a.each { |s| install_s(s) }
@word_table_s.to_a.sort{|a,b| a[1] <=> b[1]}.reverse.take(10).map{|a| [a[0].to_s, a[1]]}
end
# 両者の結果が同じかどうかチェック
def w_test
if wc1 != wc2
print "wc1 != wc2\n"
end
end
# ベンチマーク
def bm
Benchmark.benchmark(Benchmark::CAPTION, 14, nil) do |rep|
rep.report("wc with array") { wc1 }
rep.report("wc with hash") { wc2 }
end
end
# 最も多く現れた単語から10番目まで
def top10
wc2.each do |a|
print "#{a[0]}: #{a[1]}\n"
end
end
# 実行する作業を選択
# w_test
# top10
bm
このプログラムでは3つの作業を選択できます
ベンチマークの結果は次のとおりです。
user system total real
wc with array 1.345434 0.000000 1.345434 ( 1.345614)
wc with hash 0.027752 0.000000 0.027752 ( 0.027792)
ハッシュを用いたほうが圧倒的に速くその時間の比は約48:1です。 この方法が最速ではありませんが、プログラム作成の容易さから考えると有力な手法であると思います。 なお、上位10単語は以下のとおりでした。
the: 1716
and: 886
to: 827
a: 687
of: 616
it: 550
I: 545
she: 520
said: 465
in: 420
冠詞のtheが圧倒的に多く、次がandでした。 saidが9位に入っているのは物語だからでしょう。
単項マイナスとは 単項マイナスと括弧 括弧なし単項マイナスを許容する場合のBNF calcの場合
パーサ・ジェネレータとは 少し複雑な文法 四則(加減乗除)計算のBNF Racc で実装 クラス定義、BNFの記述部分 ヘッダー、インナー、フッター コンパイルと実行 演算子の優先順位と結合における左右の優先順位 まとめ
StrScanライブラリのドキュメント 字句解析とは StrScanライブラリ StrScanライブラリを使った字句解析 実例
lbtというgemを作って公開してみた lbtはどんなgemか ファイルの配置 lbt.gemspec Rakefile gemのビルド RubyGems.orgへのアップロード 補足・・rake/gempackagetaskサブライブラリについて
文字列のエンコーディングに頭を悩ませることはほとんどなくなりました。 なぜなら、どのアプリ、システムもUTF-8を使うようになったからです。 Rubyでもエンコーディングの問題が起こることはまず無いでしょう。 ですが、今回はエンコーディングの考え方を整理してみたいと思います。
Fiberを書いたときから、次はスレッドを書こうと思っていましたが、時間がかかってしまいました。 その理由は、期待したとおりのスレッドの効果がなかったためです。 今回はそのことを書きますが、これはRubyのスレッドの抱えている問題なのか、自分のやり方が悪いのかははっきりしていません。
Fiberは「ノンプリエンプティブな軽量スレッド」とRubyのマニュアルに記載されています。
今回はRubyプログラムから自動的にドキュメントを作成するRDocについて書きたいと思います。 私はこのことについて、エキスパートではありません。 この記事も、初心者の体験談だと考えてください。
Ruby/Gtkの記事を先日書いたときに、「これはかなり使える」という手応えを感じたので、WordBook(Railsで作った単語帳プログラム)のGTK 4版を作りました。 プログラムは「徒然なるままにRuby」のGitHubレポジトリに置いてあります。 レポジトリをダウンロードし、ディレクトリ_example/...
今回はGTK 3とGTK 4をRubyで使うライブラリについて書きたいと思います。
今回もRubyとGUIのトピックです。 Glimmerを取り上げます。
Rubyはグラフィックについて弱い印象があります。 しかし、グラフィックはデバイスに関することなので、言語そのものには直接の関係はないはずで、あるとすればライブラリです。 今後グラフィック関係のgemが開発されることに期待しましょう。
Rails7におけるシステムテストについて書きます。
前回作ったWordbook(リソースフル)のテストを書いてみます。 RailsのテストはminitestをRails用に拡張したものです。
今回はRailsの慣例に沿った形でWordbookを作り直します。
今回はWordBookの検索と削除についてです。
今回はRailsにおけるデータの作成と保存、そして変更について説明します。 そのベースになるモデルとデータベースの話から始め、appendとchangeの動作について詳しく説明します。
一般に、HTMLは文書の構造を表し、CSSはその体裁(見栄え)を表します。 Railsは最終的にCSSを含むHTML文書を出力するので、この2つについての理解が必須です。 この記事ではとくにCSSの人気ライブラリであるBootstrapを紹介します。 BootstrapはJavascriptも含んでいます。
Rubyの最も人気のあるアプリケーションであるRuby on Railsを取り上げようと思い、書き始めました。 予想してはいましたが、相当な分量になってしまいました。 そのため、何回かに分けて記事にすることにします。 また、対象となる読者のレベルをどうしようかと考えましたが、「徒然Ruby」が基礎的な内容から始ま...
Rubyのライブラリ管理システムのRubygemsとコマンドgemおよびbundlerについて説明します。
minitestについて連続して2回書いてきました。 「minitestはドキュメントが少ない」という人がいますが、私も同感です。 例えば、モックとスタブの説明も少ないです。 そこで、今回はmock.rbのソースコードを参考に、モックの私的ドキュメントを書いてみました。 あくまで私個人の考えであり、minites...
今回もminitestの話です。 mockとstubに焦点をあて説明します。
アプリ作成の記事でminitestを使いました。 今回はminitestについて、また一般にテストについて、私の考えを書こうと思います。
今回はメソッドの呼び出し制限ついて説明します。 呼び出し制限にはpublic、private、protectedの3つがあります。
今回は特異メソッド、特異クラス定義、名前空間、モジュール関数について説明します。
2023/10/29 追記:この記事は新しく書き直しました。 古い記事で使っていたGitHubのCalcが大幅にアップデートされたためです。 そこで、この記事に合うようなプログラムsimple_calcを新たに作りました。 このプログラムは本レポジトリの_example/simple_calcにあります。
if〜elsif〜・・・〜else〜endは皆さん良く使うでしょうか? これは場合分けで良く使われる方法です。 これと同様の制御構造にcase文があります。 Cのswitch文に似ていますが、より強力な機能を持っています。 if-else-endよりも高い能力があるといえます。
Procオブジェクトを生成するメソッドlambdaについて説明します。
今回はブロックを一般化したオブジェクトProcを説明します。
ブロック付きメソッドの作り方を説明します。
モジュールには名前空間とミックスイン(Mix-in)の2つの機能があります。 ここではミックスインについて説明します。
クラスの親子関係
Rubyの演算子とその再定義について書きます。
今回からクラスとインスタンスを定義、生成する方法を説明します
Kernelモジュールのメソッドはどこでも使うことができます。 そのメソッドの中には便利で有用なものが多いです。
ここでは私が便利だと思ったメソッドを紹介します。
実数
今回はシンボルとハッシュについて説明します。
文字列は最も使うオブジェクトのひとつです。 特にウェブ・アプリケーションでは、コンテンツだけでなくHTMLのタグやCSSを含めすべてが文字列です。 Rubyは文字列オブジェクトのメソッドが充実しており、またパターンマッチのための正規表現も充実しています。
配列は、どのプログラミング言語にもあると思います。 複数の要素を一括して扱うことができるのが配列です。 Rubyの配列はメソッドが充実しているので、プログラムを効率的、機能的に書くのに役立ちます。
今回の目標はインスタンスです。 インスタンスを説明するために、ローカル変数と文字列オブジェクトを事前に扱います。
今回はメソッド定義です。 メソッド定義はRubyの核心ですが、今回はトップレベルに限って説明します。 この限定によって、内容はかなり易しくなっています。
ブロックはRubyの特長です。 ブロックのおかげで記述が非常にすっきりと分かりやすくなります。 今回はブロックをイテレータの本体として使う方法を説明します。
ここではRubyの最も基本的なオブジェクトである整数について説明します。
「徒然なるままに」をネットで調べてみると、「することもなく、手持無沙汰なのにまかせてという意味」とありました。 まさに、自分の現状を言い当てた言葉。 しかも、ブログに書くネタもなかなか思いつかない日々。