読者です 読者をやめる 読者になる 読者になる

或るプログラマの開発日記

日々の勉強したことの備忘録なんかに使っていきます

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

stackoverflow.com

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

stackoverflow.com

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を使う上で留意する必要がありますね。

参考URL

その他の言語はどうだっけ?という事で調査してみたら、上手く纏めていただいているページがありました。非常に参考になります。

blog.mirakui.com

Rubyプログラムをプロファイラで計測する

今回は、Rubyのプロファイルを取得する機能についてのメモ。

プロファイラの実行

Rubyでは標準でプロファイルを取る機能が用意されています。ここでいうプロファイルとは、どの処理にどれ位時間が掛かってるかや、どの処理が何回呼ばれたかという分析結果です。rubyコマンドに -r profile オプションをつけて実行することで、プロファイルを取る事が出来ます。

ruby -r profile test.rb

因みに私はeclipseから実行することが主なので、eclipseから実行する場合の手順ものせておきます。

[Run As]->[Run Configurations ...]から、ArgumentsタブのInterpreter argumentsに -r profileオプションをつけます。

f:id:sishow03:20170420001724j:plain


では、物は試しということで、簡単な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

このような感じで、実行結果の後にプロファイルが出力されます。今回は単純なプログラムなので時間計測はほぼ意味が無いですが、剰余演算子の処理が何回行われたかというような情報は得られます。

各項目の意味

それぞれがどのような意味かは、リファレンスマニュアルに詳しいので引用します。

  1. 全体時間のパーセンテージ
  2. 全体時間の総和(単位は秒)
  3. 正味時間の総和(秒)
  4. 呼び出された回数
  5. 1回の呼び出し当たりの平均正味時間(ミリ秒)
  6. 1回の呼び出し当たりの平均全体時間(ミリ秒)
  7. メソッド名
https://docs.ruby-lang.org/ja/latest/library/profile.html

その他のプロファイラ

標準で利用できる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だけでなくPerlでも似たような使い方が出来るようですね。Perlは昔業務で少し触ったことがあるが、全然知らんかったわい。。(- -;)
無知というのは恐ろしいもんじゃ。。

RubyのLoggerを使ってみる

どうも、パイソンです。今回はRubyのログ出力に関する簡単なメモです。

Loggerクラスの使い方

Rubyにはどうも標準でLoggerというログ出力用ライブラリが備わっているようです。
使い勝手としてはJavalog4jと似た風な感じかなーという印象。

プログラム

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
今日は2017415日です。

雑感

Rubyとは直接関係無いけど、時間クラスの仕様って結構落とし穴になったりすることがあるので、慎重に仕様確認する必要があると思います。

たとえば私、昔お仕事でperlを使ってたことがありまして、、

my (undef, undef, undef, $mday,$mon,$year) = localtime();

とすると、$yearに+1900しないと西暦にならんかったり、$monに至っては、1月の癖に0を返却したりするんだからね!!


ということで、今回はそんな懐かしい思い出がよみがえったという記事でした。

参考URL

stackoverflow.com