37
33

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

続・Rubyのオススメの機能7選

Last updated at Posted at 2016-12-17

はじめに

この記事は Ruby Advent Calendar 2016 の 17日目です。

去年、Rubyのオススメの機能7選 という記事を書いて、バズって、はてブホッテントリ入りしました。それに気をよくして、今年も同じようなネタで勝負したいと思います。
みなさん、ストック、いいね、ブクマお願いします。

Range#bsearch 、 Array#bsearch

Array や Range はよく知られているクラスかと思います。その中でもあまり使われていないかもしれないメソッドとして bsearch メソッドがあります。

二分探索(binary search) が簡単に使えます。y = f(x) があったとき y から x を高速に求めることができます。

たとえば、下記では、二乗根を求めています。

def square(x)
  x * x
end

def root(y)
  (0..Float::INFINITY).bsearch do |x|
    y < square(x)
  end
end

p root(2) #=> 1.4142135623730951

ゼロからプラスの無限大の間で二分探索しています。

この square の例では、Math.sqrt という自明な逆関数があります。
しかし例えば、消費電力量から電気代を求める計算のように逆関数が自明でない場合もあります。
そんな場合でも Range#bsearch を使うと簡単に電気代から消費電力量を求めることができます。

Double Splat

しばしばよく使われるテクニックとして Splat があります。
実は下記の例で、find_element の引数として Splat が登場します。

* よりも無名かもしませんが、ハッシュの Splat を実現するテクニックとして ** があります。

たとえば、私も開発に関わっている電力比較サイトエネチェンジには、下記のようなコードがあります。

  def zuttomo_3_energy_charges(kw)
    summer = {month_from: 7, month_to: 9}
    winter = {month_from: 10, month_to: 6}
    stage1 = {kwh_from: 0, kwh_to: kw * 130}
    stage2 = {kwh_from: kw * 130, kwh_to: nil}
    [
      {**summer, **stage1, rate: 16.91},
      {**summer, **stage2, rate: 18.37},
      {**winter, **stage1, rate: 15.37},
      {**winter, **stage2, rate: 18.26},
    ]
  end

これは夏と夏以外で単価が違う上、契約電力に応じて第一段階と第二段階の境界が切り替わるという複雑な仕様に対応するためのコードの例です。
Hash#merge を使っても同様のことは実現できますが、** を使うことで記述がより簡潔になっています。
キーが Symbol である必要があるなど制約がありますが、うまくユースケースにマッチすると便利です。

[][]= の再定義

Ruby では演算子の再定義が可能です。
特に、 [][]= 演算子は使い方次第で、便利に思います。

例を見てみましょう。

require 'selenium-webdriver'

class SeleniumFirefoxClient
  attr_accessor :webdriver
  def initialize
    @webdriver = Selenium::WebDriver.for :firefox
    @webdriver.manage.timeouts.page_load = 10
  end

  def [](name)
    find_element(name: name).attribute("value")
  end

  def []=(name, value)
    elem = find_element(name: name)
    elem.clear
    elem.send_keys(value.to_s)
  end

  def find_element *args
    webdriver.find_element *args
  end

  def get *args
    webdriver.get *args
  end
end

client = SeleniumFirefoxClient.new

client.get("http://localhost:3000/")
p client["order[name]"] #=> ""
client["order[name]"] = "cuzic"
p client["order[name]"] #=> "cuzic"

上記の例では SeleniumFirefoxClient#[] で、ある name を持った DOM 要素の value を取得し、
SeleniumFirefoxClient#[]= である name を持った DOM 要素に対して、テキストを打ち込んでいます。
この例では、単なるテキストボックスであることを想定していますが、もう少し複雑な記述をすれば、
リストボックスやラジオボタンなどにも対応可能です。

[][]= を使うことによって、より DSL 的になっています。
値を設定するコードは特に読みやすくなっていると感じます。

Set とそのメソッド群

Ruby の標準ライブラリには、Set というクラスがあります。

Set を使うことで、集合の和、積、差などを使えます。

下記は Set を使った FizzBuzz の例です。

require 'set'

multiples_of_three = (1..100).select do |i|
  i % 3 == 0
end.to_set
multiples_of_five = (1..100).select do |i|
  i % 5 == 0
end.to_set
multiples_of_both = multiples_of_three & multiples_of_five

1.upto(100) do |i|
  case
  when multiples_of_both.include?(i)
    puts "FizzBuzz"
  when multiples_of_five.include?(i)
    puts "Buzz"
  when multiples_of_three.include?(i)
    puts "Fizz"
  else
    puts i
  end
end

上記の例は少し primitive ですが、例えば私は下記のような場合に Set を使っています。

  • あるディレクトリにあって、 zip ファイルにないファイルを列挙したいとき
  • CSV ファイルにあって、DB にない行を見つけてそれだけインポートしたいとき
  • まだ done になっていないレコードに対してだけ処理を継続したいとき
  • ActiveRecord オブジェクトにはあるが、あるページ上にない入力要素を見つけたいとき

Struct

Ruby の標準ライブラリに Struct があります。

Struct を使うと initialize や dig 、inspect など便利メソッドが最初から定義されている分、ラクができます。

具体的な例を見てみましょう。

class Tree < Struct.new(:left, :value, :right)
  def visit
    left.visit if left
    puts value
    right.visit if right
  end
end

tree =
  Tree.new(
    Tree.new(
      Tree.new(nil, :a, nil),
      :b,
      Tree.new(
        Tree.new(nil, :c, nil),
        :d,
        Tree.new(nil, :e, nil)
      )
    ),
    :f,
    Tree.new(
      nil,
      :g,
      Tree.new(
        Tree.new(
          nil,
          :h,
          nil
        ),
        :i,
        nil
      )
    )
  )

tree.visit #=>  a から i まで順に表示される

puts tree.dig(:left, :right, :value) #=> d

ここでは Struct を継承した Tree 型を使っています。
Struct.new(:left、:value、:right) となっているので、
Tree.new(left, value, right) という形式で引数をとって
Tree オブジェクトを生成することができます。
def initializeattr_accessor を記述する必要がない点が利点です。

また、ここでは Struct#dig も使っています。Hash#dig はよく知られているかもしれませんが、Struct#dig はあまり知られていないかもしれません

この例は

tree&.dig(:left)&.dig(:right)&.dig(:value)

に相当します。 tree&.dig(:left) は left が定義されていなければ nil を返し、定義されていれば、 tree.left の結果を返します。

ここでは root から考えて、左の木、右の木の値を取り出そうとしているので d が表示されています。

ほかにも Struct には便利なメソッドがありますし、足りなければ、上記の例のように自分で実装するのも可能です。
オブジェクトの構築コストが高くなるため、性能面では不利ですが、プロトタイプ作成や使い捨てのコードでは便利だと思います。

Pathname

みなさんもお使いの Ruby on Rails には Rails.root というメソッドがあります。みなさんもお使いになったことがあるでしょう。
実はこの Rails.rootPathname を返します。

Pathname には様々な便利メソッドが存在します。それを紹介します。

Pathname#children

... を除いて、そのディレクトリのすべてのディレクトリ・ファイルを、ディレクトリ付きで返します。

Pathname#exist?

File.exist?(pathname) に相当。その名前のディクトリ・ファイル等が存在するときに true を返す。

Pathname#find

Find.find(pathname) に相当。そのディレクトリ以下のディレクトリ・ファイルを再帰的に深さ優先探索を行う。

Pathname#mtime

File.mtime(pathname) に相当。最終変更日時を返す。

Pathname#sub_ext(ext)

拡張子を別の文字列で置き換える。

pathname = Pathname("source.c")
p pathname.sub_ext(".o") #=> #<Pathname:source.o>

Pathname#read

IO.read(pathname) に相当。ファイル全体を読み取り、文字列として返す。IO.read と同様に読み取るバイト数などの引数をとることもできる。

Pathname#write(body)

IO.write(pathname, body) に相当。ファイルに body を書き込む。IO.write と同様に offset などの引数をとることもできる。

ワンライナー

Ruby はワンライナーとして使うときも便利です。

特に便利な用法をいくつか紹介します。

NKF

NKF をインストールしなくても Ruby があれば日本語の文字コードの変換が簡単にできます。

たとえば、下記であれば標準入力を UTF-8 に変換します。

ruby -r nkf -pe '$_=NKF.nkf "-w", $_'

ruby -p では、各行の内容が一旦、 $_ に格納され、
スクリプトの実行後の $_ の内容が出力されます。

特定の正規表現の範囲の出力

下記はユーザ名の一覧を出力する例です。 あまり知られていないかもしれませんが、ワンライナーでは $_ =~ は省略可能です。

 cat /etc/passwd | ruby -ne 'puts $1 if /^(.+?):/'

-ane の例

区切り文字を使って分割することもできます。ある行を分割した結果は、$F の中に格納されます。

$ cat /etc/passwd | ruby -F: -ane 'puts $F.first'

なお、この場合、末尾の改行が邪魔になることがあります。
そういうときは、 -anlel を追加で指定することで改行を取り除けます。

sed と同じような使い方

下記は、拡張子 scsscss に置き換える例です。

$ find app/assets/stylesheets/ | ruby -pe 'gsub /scss$/, "css" '

ここでは Kernel.gsub を使っています。ruby -pruby -n のときにだけ定義される非推奨なメソッドですが、今でも使えます。
$_.gsub とほぼ同じですが、置換が発生したときは、$_ の内容を置き換えますので、 -p で使うと便利です。

HTTP サーバ

Ruby では、 HTTP サーバをワンライナーで簡単に立てることもできます。
これは unライブラリの一部です。

$ ruby -run -e httpd .
[2016-12-17 09:12:10] INFO  WEBrick 1.3.1
[2016-12-17 09:12:10] INFO  ruby 2.3.1 (2016-04-26) [x86_64-linux]
[2016-12-17 09:12:10] INFO  WEBrick::HTTPServer#start: pid=13173 port=8080

この例ではカレントディレクトリをドキュメントルートとして、 8080番ポートで待ち受ける形で HTTPサーバを起動します。

-run というのは un ライブラリを require するという意味です。
un は Unix コマンドの基本ライブラリを代替するポータブルな機能を提供するためのライブラリですが、その中に HTTPサーバもあるのです。

最初の3行だけ出力

下記のコードで 最初の3行だけを出力することができます。

ここで if 1..3if (1..3).include?($.) に等価です。

すなわち、行番号が1から3 の場合は true になります。

$ cat Gemfile | ruby -ne 'print  if 1..3'
source 'https://rubygems.org'

gem 'rails', '5.0.0.1'

おわりに

じっくり読むと、これまで気づいていなかったさまざまな便利な機能が存在していることに気づきます。
私自身、この投稿をするときに初めて気がついた機能がいくつもありました。
Ruby は非常に進化が速い言語ですので、これまで存在しなかった機能がどんどん追加されていきます。
常に情報をキャッチアップしていくことが大切です。

37
33
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
37
33

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?