Rubyでhashから値を取る方法のメモ
Rubyでhashを使ってて、Javaとかとは勝手が違っていたところで結構ハマッたので、調べた情報をメモ。
keyが文字列かシンボルか?
Rubyのhashはkeyとして文字列はもちろん、シンボルをkeyとして使うことも出来ます。例として、文字列、シンボルそれぞれをkeyとしたhashから値を取得するプログラムを書いてみます。
strhash={"key1" => "value1", "key2" => "value2", "key3" => "value3"} symbolhash={key1: "value1", key2: "value2", key3: "value3"} p strhash["key1"] # => "value1" p strhash[:key1] # => nil p symbolhash["key1"]# => nil p symbolhash[:key1] # => "value1"
もちろん、文字列とシンボルは中身が全然異なるので、それを混同するとおかしなことになります。keyが何の型かを認識しておかなければなりません。
fetchメソッドの仕様
先の例では[]を使って値を取得していましたが、fetchメソッドを使って値を取得することも出来ます。[]を使うのと、fetchを使うのとでは、hashから値が取れなかった時の扱いに大きな違いがあります。
単純なfetchメソッド
keyのみを引数とするfetchメソッドは、値が取れなかったときに例外が発生します。
symbolhash={key1: "value1", key2: "value2", key3: "value3"} p symbolhash.fetch(:key1) # => "value1" p symbolhash.fetch(:key4) # => `fetch': key not found: :key4 (KeyError)
値が絶対に取得できる前提で使うにはいいのかもしれませんね。
デフォルト値付きfetch()
第2引数にデフォルト値を渡すと、値が取れなかったときにデフォルト値が返却されます。
symbolhash={key1: "value1", key2: "value2", key3: "value3"} p symbolhash.fetch(:key1, "not found") # => "value1" p symbolhash.fetch(:key4, "not found") # => "not found"
[]で取るのと、fetch(key, nil)とは同じということですね。
ブロック付きfetch()
ブロックを渡すと、値が取れなかったときにブロックの値を評価した値が返却されます。
symbolhash={key1: "value1", key2: "value2", key3: "value3"} p symbolhash.fetch(:key1){|key| key.to_s + " not found"} # => "value1" p symbolhash.fetch(:key4){|key| key.to_s + " not found"} # => "key4 not found"
うーん。。正直使いどころが良く分からんが、まーこういう使い方も出来るということですね。
参考URL
RubyでStreamから渡された数値群をFixnumに変換する方法
CodeIQ、paiza等で出るプログラミングの問題では、標準入力から渡された数値データを処理する事が多いですが、標準入力経由だと基本文字列として受け取る為、計算に利用するにはプログラム内部で数値型に変換する必要があります。
この変換処理をいちいち書くのが結構おっくうだったりするのですが、Rubyでは結構簡単に処理を記述することができたので、ここにメモしておきます。
文字で与えられた数値群を一括変換
例として、標準入力から与えられた数値群を全て足した値を標準出力に出力するプログラム書いてみます。
先ずは、ダメな例から。
(a, b, c)=DATA.gets.split puts a + b + c __END__ 10 20 30
こいつを実行すると、
102030
という結果になります。単に文字を連結してるだけですね(^^;)
とりあえず動かす為の修正方法として、
puts a.to_i + b.to_i + c.to_i
とすれば目先要件は満たせるでしょうが、如何にも書き方が野暮ったいという感じがします。
to_iの処理を何回も書かないような記述方法がこれ。
(a, b, c)=DATA.gets.split.map(&:to_i) puts a + b + c __END__ 10 20 30
実行すると、
60
というわけで、それぞれ問題なく数値型に変換されています。
map(&:to_i)のやっていること
初めてこのような記述を見て、何がなんだか訳分からん状態だったので、自分なりに調べた内容でこれを要約すると、
:to_iシンボルに対するto_procメソッドの結果として返ってきたProcオブジェクトをブロックとしてmapメソッドに渡す。
という事になります。
ちょっとややこしいので細かく分解してみましょう。
先ず、mapメソッドのやっている事。
mapメソッド
mapメソッドは、要素の数だけ繰り返しブロックを実行し、ブロックの戻り値を集めた配列を作成して返します
http://ref.xaio.jp/ruby/classes/array/map
次に、(&:to_i)の部分の、「&」の意味。
ブロックの部分だけを先に定義して変数に保存しておき、後からブロック付きメソッドに渡すことも出来ます。 それを実現するのが手続きオブジェクト(Proc)です。 それをブロックとして渡すにはブロック付きメソッドの最後の引数として `&' で修飾した手続きオブジェクトを渡 します。
https://docs.ruby-lang.org/ja/2.2.0/doc/spec=2fcall.html#block
ま、mapに手続きオブジェクトを渡しているというのは確かなようです。
最後に、「&」の後に続く:to_iの意味。
これはSymbolと呼ばれるオブジェクトです。しかし「&」に続く物としては手続きオブジェクトが期待されているので型が合いません。
そのためSymbolが持つto_procメソッドで暗黙的にProcオブジェクトに変換されているのです。
https://docs.ruby-lang.org/ja/2.2.0/class/Symbol.html#I_TO_PROC
以上が、map(&:to_i)のやっている事になります。
雑感
今回のケース、正直ソースの見た目も内部でやっている事も大分ややこしいんですが、to_procが省略されているという事が発見出来れば理解するのも難しくないかなぁという感じです。
参考URL
Rubyにおける「真」と「偽」の判定
Ruby及びその他の言語に於ける真と偽の扱いのメモ。
「真」と「偽」の扱い
if文やwhile文での条件判定に使う「真」と「偽」の扱いはプログラム言語により様々だったりします。
例えばC言語ではint型の0が「偽」であり、それ以外が「真」です。Java言語ではプリミティブ型であるbooleanが用意されており、trueが「真」、falseが「偽」として扱われます。
では、Rubyはどうなの?ということで調べて見ますと、Rubyではfalseとnilが「偽」、それ以外が「真」として扱われます。
実際の挙動を見てみましょう。
プログラム
if nil puts "nil is true" else puts "nil is false" end if false puts "false is true" else puts "false is false" end if 0 puts "0 is true" else puts "0 is false" end if 1 puts "1 is true" else puts "1 is false" end
実行結果
nil is false false is false 0 is true 1 is true
このように、0が「偽」扱いにはならないという点はRubyを使う上で留意する必要がありますね。
Rubyプログラムをプロファイラで計測する
今回は、Rubyのプロファイルを取得する機能についてのメモ。
プロファイラの実行
Rubyでは標準でプロファイルを取る機能が用意されています。ここでいうプロファイルとは、どの処理にどれ位時間が掛かってるかや、どの処理が何回呼ばれたかという分析結果です。rubyコマンドに -r profile オプションをつけて実行することで、プロファイルを取る事が出来ます。
ruby -r profile test.rb
因みに私はeclipseから実行することが主なので、eclipseから実行する場合の手順ものせておきます。
[Run As]->[Run Configurations ...]から、ArgumentsタブのInterpreter argumentsに -r profileオプションをつけます。
では、物は試しということで、簡単なfizzbuzz問題のプログラムのプロファイルを取ってみましょう。
プログラム
(1..100).each do |x| if x % 15 == 0 then puts "FizzBuzz" elsif x % 3 == 0 then puts "Fizz" elsif x % 5 == 0 then puts "Buzz" else puts x end end
実行結果
1 2 Fizz 4 Buzz . . 中略 . . Fizz 97 98 Fizz Buzz % cumulative self self total time seconds seconds calls ms/call ms/call name 0.00 0.00 0.00 1 0.00 0.00 TracePoint#enable 0.00 0.00 0.00 3 0.00 0.00 Thread.current 0.00 0.00 0.00 1 0.00 0.00 Mutex#lock 0.00 0.00 0.00 1 0.00 0.00 MonitorMixin#mon_enter 0.00 0.00 0.00 1 0.00 0.00 Kernel#respond_to_missing? 0.00 0.00 0.00 1 0.00 0.00 Kernel#respond_to? 0.00 0.00 0.00 1 0.00 0.00 Gem.suffixes 0.00 0.00 0.00 4 0.00 0.00 Gem.find_unresolved_default_spec 0.00 0.00 0.00 1 0.00 0.00 Array#each 0.00 0.00 0.00 1 0.00 0.00 Gem::Specification.unresolved_deps 0.00 0.00 0.00 1 0.00 0.00 MonitorMixin#mon_check_owner 0.00 0.00 0.00 1 0.00 0.00 Mutex#unlock 0.00 0.00 0.00 1 0.00 0.00 MonitorMixin#mon_exit 0.00 0.00 0.00 4 0.00 0.00 IO#set_encoding 0.00 0.00 0.00 2 0.00 0.00 IO#sync= 0.00 0.00 0.00 1 0.00 0.00 Kernel#gem_original_require 0.00 0.00 0.00 1 0.00 0.00 Kernel#require 0.00 0.00 0.00 261 0.00 0.00 Fixnum#% 0.00 0.00 0.00 261 0.00 0.00 Fixnum#== 0.00 0.00 0.00 53 0.00 0.00 Fixnum#to_s 0.00 0.00 0.00 200 0.00 0.00 IO#write 0.00 0.00 0.00 100 0.00 0.00 IO#puts 0.00 0.00 0.00 100 0.00 0.00 Kernel#puts 0.00 0.00 0.00 100 0.00 0.00 nil# 0.00 0.00 0.00 1 0.00 0.00 Range#each 0.00 0.00 0.00 1 0.00 0.00 TracePoint#disable 0.00 0.01 0.00 1 0.00 10.00 #toplevel
このような感じで、実行結果の後にプロファイルが出力されます。今回は単純なプログラムなので時間計測はほぼ意味が無いですが、剰余演算子の処理が何回行われたかというような情報は得られます。
各項目の意味
それぞれがどのような意味かは、リファレンスマニュアルに詳しいので引用します。
https://docs.ruby-lang.org/ja/latest/library/profile.html
- 全体時間のパーセンテージ
- 全体時間の総和(単位は秒)
- 正味時間の総和(秒)
- 呼び出された回数
- 1回の呼び出し当たりの平均正味時間(ミリ秒)
- 1回の呼び出し当たりの平均全体時間(ミリ秒)
- メソッド名
その他のプロファイラ
標準で利用できるprofile以外にも、ruby-profという軽量なプロファイラ等いろいろあるようなので使って見ようと思います。
DATA変数と__END__トークンがテストに便利
今回は、ちょっとしたRubyプログラムのテストに便利なDATA変数の使い方のメモです。
DATA変数とは
Rubyプログラムファイルに__END__トークンだけの行が含まれている場合、その次の行以降にアクセスする為のストリームとしてDATA変数が定義されます。
この機能を使うと、paizaやCodeIQの課題なんかでよくある複数行の標準入力データを処理するプログラムを作る際に非常に便利だったりします。
__END__トークン以降に、テストしたいデータを貼り付けておけば、いちいち実行してからコンソールにデータを貼り付けるなんて面倒な事せずに済みますから!
使い方
というわけで、複数行の入力データを元にHashを作るという簡単な実装例を作ってみました。
プログラム
$_input = DATA #標準入力に切り替える場合、STDINに変更する h = {} while line = $_input.gets do (k, n) = line.split(" ") h.store(k, n) end p h __END__ 1 apple 2 orange 3 lemon 4 banana
実行結果
{"1"=>"apple", "2"=>"orange", "3"=>"lemon", "4"=>"banana"}
RubyのLoggerを使ってみる
どうも、パイソンです。今回はRubyのログ出力に関する簡単なメモです。
Loggerクラスの使い方
Rubyにはどうも標準でLoggerというログ出力用ライブラリが備わっているようです。
使い勝手としてはJavaのlog4jと似た風な感じかなーという印象。
プログラム
require 'logger' log = Logger.new('./logsample.log') log.debug("debug log") log.info("info log") log.warn("warn log") log.error("error log") log.fatal("fatal log") #INFO以上のレベルのみ出力 log.level=Logger::INFO log.debug("debug log") log.info("info log") log.warn("warn log") log.error("error log") log.fatal("fatal log")
出力結果(logsample.log)
# Logfile created on 2017-04-16 23:45:50 +0900 by logger.rb/47272 D, [2017-04-16T23:45:50.273300 #9628] DEBUG -- : debug log I, [2017-04-16T23:45:50.273300 #9628] INFO -- : info log W, [2017-04-16T23:45:50.273300 #9628] WARN -- : warn log E, [2017-04-16T23:45:50.273300 #9628] ERROR -- : error log F, [2017-04-16T23:45:50.273300 #9628] FATAL -- : fatal log I, [2017-04-16T23:45:50.273300 #9628] INFO -- : info log W, [2017-04-16T23:45:50.273300 #9628] WARN -- : warn log E, [2017-04-16T23:45:50.273300 #9628] ERROR -- : error log F, [2017-04-16T23:45:50.273300 #9628] FATAL -- : fatal log
という感じで出力することができます。
なお、
log = Logger.new('./logsample.log')
のところを
log = Logger.new(STDOUT)
に変えてみると、標準出力に出すことが出来ます。
Loggerクラスの機能等
そういや、ログローテート機能はどうなってんだろ?と思い調べてみたらちゃんと機能として備わっている模様。
参考URL
Rubyist Magazine - 標準添付ライブラリ紹介 【第 2 回】 Logger
今更ながら、標準で大抵必要な機能が備わっているのがRubyの良さだと感心する。
RubyのTimeクラスを使ってみた
どうも、パイソンです。今回はRubyのTimeクラスのメモです。
Timeクラス
Rubyで現在時間を取るにはTimeクラスを使う。
使い方もRubyらしく結構シンプル。
プログラム
puts Time.new t = Time.new puts "今日は" + t.year.to_s + "年" + t.month.to_s + "月" + t.day.to_s + "日です。"
実行結果
2017-04-15 22:49:35 +0900 今日は2017年4月15日です。
雑感
Rubyとは直接関係無いけど、時間クラスの仕様って結構落とし穴になったりすることがあるので、慎重に仕様確認する必要があると思います。
たとえば私、昔お仕事でperlを使ってたことがありまして、、
my (undef, undef, undef, $mday,$mon,$year) = localtime();
とすると、$yearに+1900しないと西暦にならんかったり、$monに至っては、1月の癖に0を返却したりするんだからね!!
ということで、今回はそんな懐かしい思い出がよみがえったという記事でした。