(1..7).to_a.sort { |a, b| a[0] b[0] } ?

イテレータ解説である
PHPer 向け Ruby のイテレータ解説 - まちゅダイアリー(2007-10-11)
を読んでいて、気になる表記を見かけた。

ちなみに、イテレータの説明のために array_map メソッドを自作したけど、これらのメソッドは Array クラス(がインクルードしているEnumerableモジュール)にてあらかじめ用意されている。なので、自分で array_map メソッドを作らなくてもそのまま以下のように書ける。

arr = [1,2,3,4,5,6,7]

arr.map {|item| item * 3 }
arr.map {|item| item + 3 }
arr.map {|item| item % 3 }

arr.select {item| item > 10 }
arr.sort {|a,b| a[0] <=> b[0] }

よくやるsortのイテレータ

sort {|a,b| a <=> b }

なので、[0]が有るか無いかの違い。
で、実際にやってみるとこうなる。

pry(main)> arr = [1,2,3,4,5,6,7]
=> [1, 2, 3, 4, 5, 6, 7]
pry(main)> arr.sort {|a,b| a[0] <=> b[0] }
=> [4, 2, 6, 1, 5, 3, 7]

…なんなの、この並び方は!?

pry(main)> arr.sort {|a,b| a[1] <=> b[1] }
=> [1, 5, 4, 3, 2, 6, 7]
pry(main)> arr.sort {|a,b| a[0] <=> b[1] }
=> [4, 1, 7, 2, 6, 5, 3]

???

{ |a, b| a[0] <=> b[0] }?

ブロックとともに呼び出された時には、要素同士の比較をブロックを用いて行います。 ブロックに2つの要素を引数として与えて評価し、その結果で比較します。
instance method Array#sort (Ruby 1.9.3)

つまり

a[0] <=> b[0]

の比較が行われている。
なんだろ、これ。
当初、私はa, bそれぞれが複数要素を持つのかと思った。つまり、条件によってブロックが要素を複数取り出したりするのかなと考えてみた*1。でも違った。

結論

instance method Fixnum#[]

self[nth] -> Fixnum[permalink]

nth 番目のビット(最下位ビット(LSB)が 0 番目)が立っている時 1 を、そうでなければ 0 を返します。

  • [PARAM] nth:

何ビット目を指すかの数値

  • [RETURN]

1 か 0
instance method Fixnum#[] (Ruby 1.9.3)

つまり2進数にしてn番目のビットで比較を行っていたわけ。整数クラスに[]が定義されてることを知らなかったし、そんな定義をしているとは思わなかった。

ではStringクラスでは?

ここからは脱線ですw

instance method String#[]

self[nth] -> String | nil[permalink]
slice(nth) -> String | nil

nth 番目の文字を返します。 nth が負の場合は文字列の末尾から数えます。 つまり、 self.size + nth 番目の文字を返します。
nth が範囲外を指す場合は nil を返します。
instance method String#[] (Ruby 1.9.3)

そういえば文字列の文字を返すんですね。なので次の結果もすぐわかる。

pry(main)> arr = ["g", "f", "e", "d", "c", "b", "a"]
=> ["g", "f", "e", "d", "c", "b", "a"]
pry(main)> arr.sort{ |a, b| a <=> b }
=> ["a", "b", "c", "d", "e", "f", "g"]
pry(main)> arr.sort{ |a, b| a[0] <=> b[0] }
=> ["a", "b", "c", "d", "e", "f", "g"]
pry(main)> arr.sort{ |a, b| a[1] <=> b[1] }
=> ["g", "f", "e", "d", "c", "b", "a"]
pry(main)> arr.sort{ |a, b| a[1] <=> b[0] }
ArgumentError: comparison of String with String failed

sort{ |a, b| a[1] <=> b[1] }はnilnilを比較しているのでソートしない。
sort{ |a, b| a[1] <=> b[0] }でエラーが出るのはStringとnilを比較しようとしているからですね。

納得しました^_^。

*1:こんな器用なことはしてくれないようだw

rubyで画像の拡大縮小(RMagickの拡大縮小関係メソッドの違い)

RMagick

RMagickでは画像を

require 'RMagick'
img = Magick::Image.read('original.png').first

として読み込みます*1
RMagickには拡大縮小のメソッドとして何種類かあります。パラメータとしては拡大(縮小)率scaleを指定、または縦横ピクセル(width, height)を指定、の二種類があります。

  • resizeメソッド

resizeメソッドは,(元画像によるところはありますが)画質もよいですがファイルサイズは大きくなりがちです。また,リサイズの計算を行うためのフィルタも指定ができ,処理内容を調整することができます。

  • scaleメソッド

scaleメソッドは,resizeよりも画質がやや悪くファイルサイズはやや小さくなる傾向があります。

  • thumbnailメソッド

thumbnailメソッドは,本来元画像の10%より小さい画像を作るためのメソッドで,ファイルサイズは画像のサイズにしては大きくなる傾向があります。ですが,実行速度は速くできます。

  • sampleメソッド

sampleメソッドは,通常リサイズの場合に発生する中間色を一切作らないため,実行速度は速くファイルサイズも小さくできますがこの中で一番画質を犠牲にしています。
第26回 RMagickを用いた画像処理(1)リサイズ:Ruby Freaks Lounge|gihyo.jp … 技術評論社

Christy(ImageMagickのチーフアーキテクトだ)はこれら4つのメソッドの違いを次のように説明している:
各メソッドの特徴(引用註:上記のメソッド順に並び替えました)

resize まぁ、あれだ。何でもありのメソッドだ。使用するフィルタを選んだり、ぼかしたりシャープにしたりもできる。フィルタは縮小時にはLanczosFilter、拡大時はMitchellFilterをデフォルトで使用する。代表色を作るとき、実は隣接ピクセルのアルファチャネル情報も考慮している。透明度の低いピクセルは高いピクセルよりも重みを多く持つ。
scale sampleに似ているが、浮動小数点によるサンプリングを実施し、周囲のピクセル情報も代表色に取り入れている。
thumbnail 範囲内のピクセルを5点サンプリングし、その情報を元に代表色を決定・リサイズする。resizeメソッドで画像を1/5のサイズに縮小するときと同じ。プロファイルやコメント情報はstripされる。
sample 範囲内の中央ピクセルの情報だけを使ってリサイズする(つまり、画像中に無い色は生成されない)。resizeをPointFilterで使用したときと同じ。

Making Thumbnails with RMagick - selflearn @ ウィキ - アットウィキ

画質は主観になるけど、sampleメソッドはまず使うことはないだろうと思えるくらい汚い。それ以外の3つは大差ない感じに見えた。
んでそれぞれのファイルサイズの結果は以下。すべてにおいて、scaleメソッドがファイルサイズ最小値となった。
これらの結果から、個人的には、実行速度よりファイルサイズを小さくしたいときはscaleメソッドを、名前のとおり速度を重視したサムネイル画像を作るならthumbnailメソッドを使うのが良いかなぁと思った。
RubyのRMagickで画像をリサイズする - アインシュタインの電話番号

これらの引用では、各メソッドを遅い順に並べている、のかと思います。
resizeは良いフィルタを使うので遅い、のですかね。

メソッドによる画質比較

http://rmagick.rubyforge.org/resizing-frame.html
を参照。

resizeで速く処理

JPEGファイルの場合には、ファイル自身の特徴として8x8単位でタイリングされてます。のでImageMagickの場合は、次のようにすることで速くなるそうです。

convert -define jpeg:size=180x120 -resize 180x120 src.jpg dst.jpg
本当は速いImageMagick: サムネイル画像生成を10倍速くする方法 - 昼メシ物語

各辺の長さとして1/2, 1/4, 1/8、面積比にすると1/4, 1/16, 1/64である画像を、オリジナルの画像の代わりにして処理を行うので、メモリ消費が少なくて処理速度が上がるようです。

サムネイル化したい画像が巨大ファイルサイズのJPEGデータだった場合、全データの読み込み&画像処理よりもっと良い方法がある。
それはImage::Infoの属性self.sizeを指定してファイルを読むことだ。求めているサムネイルサイズのちょうど2倍のサイズを指定してから読み込むと、ImageMagickJPEGライブラリはファイルのデータ全部を読み込まず、必要な量だけを読み込んでくれるというわけだ。その後、適度に小さくなって読み込まれた画像を使ってサムネイル化する。
大きなファイルの読み込みはメモリを圧迫して無駄も多いので、ぜひ活用してほしい。
Making Thumbnails with RMagick - selflearn @ ウィキ - アットウィキ

これをRMagickでもやってみましょう。

  • ふつうにresize
Magick::Image.read("image.jpg").first.resize(scale).write("out.jpg")
  • オンメモリ縮小resize(image.jpgのサイズはMagick::Image.pingで求めて(w1, h1)、縮小後out.jpgのサイズは(w2, h2)に)
img_ping = Magick::Image.ping("image.jpg").first
h1 = img_ping.rows
w1 = img_ping.columns
img = Magick::Image.read("image.jpg").first{self.define("jpeg", "size", w2 + "x" + h2)}
img.resize(h2 / img.rows).write("out.jpg")

ここでimgは、(w2, h2)よりも大きいサイズの縮小image.jpgになるのでimg.rowsはh1ともh2とも異なります。なので縮小率scaleはh2 / img.rowsとして算出する必要があります。

速くなる?

少しサンプルを取って確認したところ、所要時間が4〜6%ほど少なくなりました。

*1:クラスメソッドreadは画像を要素とする配列を返しますので、firstメソッドで画像のみを取り出します。マニュアルに書いてあるんだけど読めてなくて苦労したorz