RubyのComparableモジュールで比較を実装

Effective Ruby

Effective Ruby

項目13 "<=>"とComparableモジュールで比較を実装しよう

例にでてくるのはバージョンの比較を実装する時の話。
例えば、"10.10.3"と"10.9.8"のような比較の場合どうするか?

Object#<=>

2つのオブジェクトが等しいかどうかをテストするだけの汎用メソッド。

class Version
  attr_reader(:major, :minor, :patch)

  def initialize(version)
    @major, @minor, @patch = version.split(".").map(&:to_i)
  end
end


>> %w(10.10.3 10.9.8).map { |v| Version.new(v) }.sort
ArgumentError (comparison of Version with Version failed)

# オブジェクトが等しくなければnilを返す
>> v1 = Version.new("10.10.3")
>> v2 = Version.new("10.9.8")
>> v1 <=> v2
=> nil

なので<=>を実装する

class Version
  attr_reader(:major, :minor, :patch)

  def initialize(version)
    @major, @minor, @patch = version.split(".").map(&:to_i)
  end

  # 追加
  def <=>(other)
    return nil unless other.is_a?(Version)

    # 各桁を比較し0でないものがあればその値を返す。全部0なら0を返す。
    [ major <=> other.major,
      minor <=> other.minor,
      patch <=> other.patch,
    ].detect { |n| !n.zero? } || 0
  end
end

>> Version.new("10.10.3") <=> "a"
=> nil

>> Version.new("10.10.3") <=> Version.new("10.11.3")
=> -1

>> Version.new("10.11.3") <=> Version.new("10.10.3")
=> 1

>> Version.new("10.10.3") <=> Version.new("10.10.3")
=> 0

ついでに比較演算子

"<", "<=", "==", ">", ">=" はComparableモジュールをincludeすればok。

class Version
  include Comparable  # これ追加
  attr_reader(:major, :minor, :patch)

  def initialize(version)
    @major, @minor, @patch = version.split(".").map(&:to_i)
  end

  def <=>(other)
    return nil unless other.is_a?(Version)

    [ major <=> other.major,
      minor <=> other.minor,
      patch <=> other.patch,
    ].detect { |n| !n.zero? } || 0
  end
end

>> v1 = Version.new("10.10.3")
>> v2 = Version.new("10.11.3")
>> [v1 < v2, v1 <= v2, v1 == v2, v1 >= v2, v1 > v2]
=> [true, true, false, false, false]

>> Version.new("10.10.10").between?(v1, v2)
>= true

覚えておくべき事項

  • オブジェクトの順序は、"<=>演算子を定義し、Comparableモジュールをインクルードして実装しよう
  • "<=>"演算子は、左日演算子が右日演算子と比較できないものならnilを返す。
  • クラスのために"<=>"を実装した場合、特にインスタンスをハッシュキーとして使うつもりなら、eql?を"=="の別名にすることを検討しよう。 別名にする場合には、hashメソッドもオーバーライドしなければならない。

最後のやつは、こんな感じに実装すると良い。

class Version
  alias_method(:eql?, :==)

  def hash
    [major, minor, patch].hash
  end
end

facebookのosqueryがキモすごい

プロセスとかハードウェアの情報をSQL likeに取得できるすごいツール。
詳しく読んでないですが、CLIだけでなくdaemonとして動かして結果も記録できるとのこと。
osquery | Easily ask questions about your Linux, Windows, and macOS infrastructure

物によってはコマンド打つより、見やすくていいかもしれない。
 

こんな感じ

SELECT DISTINCT process.name, listening.port, process.pid 
FROM processes AS process JOIN listening_ports AS listening 
ON process.pid = listening.pid WHERE listening.address = '0.0.0.0';

f:id:rochefort:20180216133938p:plain

他には

以下にテーブルがまとまってます。現時点で194テーブル。
osquery | Schema
 
(いつからかMacで見れなくなっていたbluetoothバイスの電池残とか見れないかなぁと探して見たのですが、見つかりませんでした。)

Amazon Echo Dot(Alexa)とNode-REDで家電をスマートホームに対応させる

最近、Echo(Alexa)をそこそこ使っています。
特にエアコンのオン・オフは便利でおすすめです。
Amazon Echo Dot(Alexa)を活用してみる

 

IFTTT + トリガーの問題点

便利は便利なのですが、 現状は は、IFTTT + トリガーで実行しているため、最後に「トリガー」と言わなければいけませんでした。
 
具体的には、「Alexa エアコンオン トリガー」みたいな感じでした。
すごい不自然です。

Node-RED でスマートホームに対応させる

スマートホームに対応させることで「Alexa エアコンつけて」という感じでとても自然な言い回しになります。
 
通常スマートホームは、家電側が対応していないと利用できないのですが、Node-RED Alexa Home Skill Bridge を利用することで、スマートホームに登録することができます。Alexaの呼び出しをNode-RED側で受けてコマンド実行(今回はcurlでpost)することでiRkitから赤外線を送信し家電を操作することができます。
 

概要図

ざっくりこんな感じ。
Node-REDの実行環境はRaspberry Piを利用しています。
f:id:rochefort:20180214201452p:plain

 

参考

 

やり方ざっくり

細かいやり方は、上記リンクに記載されているので割愛します。

  1. Node-RED Alexa Home Skill Bridgeのエンドポイントにユーザ・デバイス情報の登録
  2. AlexaアプリでスマートホームスキルにNode-REDを追加、アカウントのリンク、デバイスを追加
  3. Node-RED を実行環境(Raspberry Pi)にインストールしデーモンとして実行、Node-REDのフローを作成

Node-RED Alexa Home Skill Bridge

肝は Node-RED Alexa Home Skill Bridge で、ここにデバイスを登録することで、Alexaアプリのスマートホームにデバイスを追加することができ、後述のNode-REDでフローを定義することができます。
なのでユーザ登録およびデバイスの登録が必要です。
 
バイス登録例:
f:id:rochefort:20180214115805p:plain

バイス情報の使われ方

  1. Alexaアプリを通してスマートホームに登録する際にこのエンドポイントの情報を引っ張って来る
  2. Node-REDのノードとエンドポイントのデバイスを紐付ける

 

Node-RED

Node-RED日本ユーザ会

Node-REDはハードウェアデバイス/APIおよびオンラインサービスを接続するためのツールです。

GUIでフローを作成することでイベントドリブンなアプリケーションを作成することができます。すごい便利そう!!

フロー例:
f:id:rochefort:20180214121723p:plain
今回は家電のON/OFFを制御するのみ。
この末端のON/OFFのノードにコマンドを登録します。具体的には、iRKitのAPIを叩くcurlコマンド。

iRKitのcurl は以下参照。

IRKit - Open Source WiFi Connected Infrared Remote Controller

$  curl -i "http://x.x.x.x/messages" -H "X-Requested-With: curl" -d '{"freq":xx,"data":...}'

のような感じで実行できます。
試していないのですが、Node-RED 側でHTTPのRequestも飛ばせるようなので、おそらくそれでも動くと思います。

See Also

Alexa活用術

Siriの場合

同様にHomebridgeを使うことで対応可能です。