はちゅにっき

こっちのブログはまったり更新

Enumerable#each_with_object でハマった

[1, 2, 3].each_with_object(Array.new) do |i, memo|
  memo += [i, i * 2]
end
#  => [ ]

あれ!?

[[1, 2], [2, 4], [3, 6]]

になると思ったのに!?

qiita.com qiita.com

もうさんざん既出ですね! Enumerable#each_with_object は引数に対して破壊的な変更を加えなければダメなんですね。 つまり

[1, 2, 3].each_with_object(Array.new) do |i, memo|
  memo.push([i, i * 2])
end

のように破壊的なメソッドを利用すれば OK ですね。 もしくは今回のケースだと Enumerable#inject を利用して、以下のようにすれば OK ですね。

[1, 2, 3].inject(Array.new) do |memo, i|
  memo + [i, i * 2]
end

ハマったのでメモとして。

ブロック内でのみ有効な Redis のインスタンスを起動したい

最近は Ruby をちょくちょく使っています。

Ruby で Redis を使ったテストを書く場合は fakeredismock_redis を使うのが一般的かと思いますが、ちょっとだけ Fake や Mock ではなく本物の Redis を使いたい時ってありませんか?

例えば Redis やその周辺ライブラリの挙動をちょっと確認したい時とか、手元で Redis の RDB だけを仕込んでおきたい時とか…

ないですね。

と、とにかく、そんな要望が個人的にはあったので、Ruby の勉強を兼ねつつ Redispot という Gem を作ってみました。

github.com

Redispot は引数としてブロックを取り、そのブロック内でのみ有効な Redis のインスタンスを起動します。こんな感じ。

require 'redis'
require 'redispot'

redis = nil

Redispot::Server.new do |connect_info|
  # このブロック内でのみ Redis が起動する
  redis = Redis.new(connect_info)
  redis.ping  # => "PONG"
end

# ブロックの外なので無効
redis.ping  # => raise Errno::ENOENT

上記例ではコンストラクタにブロックを渡して実行していますが、Redispot::Server#start というメソッドもあり、こちらにブロックを渡すこともできます。

server = Redispot::Server.new
server.start do |connect_info|
  # このブロック内でのみ Redis が起動する
  redis = Redis.new(connect_info)
  redis.set("key", "value")
  redis.save
end

server.start do |connect_info|
  # このブロックの Redis は上記とは別の新規インスタンス
  redis = Redis.new(connect_info)
  redis.get("key")  # => nil
end

コンストラクタredis.conf に指定したいパラメータを Hash で渡すことができるので、以下のように指定すれば、Redis の RDB を手元に残すこともできます。

config = {
  dir: File.expand_path('../', __FILE__),
  dbfilename: 'dump.rdb',
}
# Ruby のスクリプトと同じ場所に "dump.rdb" というファイルが残る

Redispot::Server.new(config: config) do |connect_info|
  redis = Redis.new(connect_info)
  redis.set('key', 'value')
  redis.save  # save を忘れると dump.rdb に保存されないよね
end

スポットインスタンスのように「ちょっとだけ使う時のために」ということで Redispot と適当に名前をつけましたが、元ネタは @typester さんが書いて @songmuさんが現在メンテナンスをしている PerlTest::RedisServer というモジュールです。

github.com

ということで、もし、もし、仮にちょっとだけ Redis インスタンスが使いたいなぁ。と思うことがあればご利用ください。

本当に PHP の DoS 脆弱性 (CVE-2015-4024) キツくない?

hakaikosen.hateblo.jp
上記記事を「あら大変(棒読み)」とか思いながら読んでいたけれど、PHPBTS の方を読んでみたら確かに原理から再現手順まで細かく記載されていて
「なんかこれまずそう」と思ったので、docker を使って検証してみることに。

続きを読む

mp3 の ID3 Tag をファイルパスに基づいて更新する

アーティスト名/アルバム名/01-曲名.mp3

というファイルパスのルールで保存してある mp3 に対して、ファイルパスに基づいて適切な ID3 タグを一括で設定する Script を Rubytaglib-ruby を使って書いてみました。

taglib-rubyC++ で書かれた ID3 Tag 編集用のライブラリ TagLibRuby バインディングなので、別途 TagLib 本体をインストールする必要があります。
インストール方法は上記 taglib-ruby のページに OS 別に記載されていますので割愛。
あとは、bundler 用に以下のような Gemfile を書いてインストールすれば OK。

できあがったプログラムはこんな感じ。

あとは、対象のディレクトリを指定して (複数指定可) 実行するだけ。

$ ./updid3.rb /path/to/artist/album /other/artist ./music

これで指定したディレクトリ以下の拡張子が ".mp3" のファイルを探し出し、ファイルパスに基づいた ID3 Tag を設定します。
適当に --dry-run オプションも実装していて、これを渡すと ID3 Tag がどう設定されるかの表示だけを行い、実際の処理は行いません。

$ ./updid3.rb --dry-run /path/to/artist
Artist: アーティスト名
 Album: アルバム名
 Title: 曲名
 Track: 1
--------------------------------------------------
(...略...)

そんなわけで、無事に古い CD に対しても ID3 タグを埋め込むことができました。
やったね。

String#to_s と 文字列リテラル内の式展開と - その3

もうここまでくるとただの興味本位。

文字数・桁数によってどんな感じに処理時間が増加するのかを調べてみました。 今回は桁数を 1桁から100桁まで増加させたときの処理時間の変化。

グラフにしたらこんな感じ。

http://f.st-hatena.com/images/fotolife/m/magicalhat/20150218/20150218170902_original.png

ノイズっぽいデータが一部あるけれど、String / Symbol は文字数が与える影響はごくごくわずか。 Fixnum は桁数に応じて処理時間が延びる。みたいな感じですか?正比例で伸びるのかと言われると、なんか違いそうな感じはするけれど。。。

特に驚きもなくそんな感じでした。おしまい。

String#to_s と 文字列リテラル内の式展開と - その2

Float と Symbol についても気になったので追加してみました。

どれも予想通り Object#to_s の方が "#{Object}" の書き方よりも早いですね。

Float#to_s のコストは他と比べると比較的高いんですね。 といっても 100万回やってこの値なので、本当に気にする必要があるかはよく考えた方がいいですね。

追記

「Float が小数点を考えると1文字分多いんじゃないか?」と。

なるほどごもっとも。

ということで、1桁減らしてもう一度

そうね。早くなったね。ほんの少し。

ただの興味本位でやっていることなので、念のためもう一度。

100万回やってこの値なので、本当に気にする必要があるかはよく考えた方がいいですね。

本当に必要な人もいると思うけれどね。

桁数でどう変わるかもちょっと調べてみよっと。

String#to_s と 文字列リテラル内の式展開と

やっぱ、こういうものですよね。

String#to_s (文字列を文字列にする) か Fixnum#to_s (数値を文字列にする) かでも、当然だけれど違いは出るんですね。