ためすう

Ruby で %Q を使ってみる

2019-07-05

やったこと

Ruby で %Q を使ってみます。

確認環境

$ ruby --version
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-darwin17]

調査

ダブルクオートの文字列と同等です。

%Q を使ったもの

$ irb
irb(main):001:0> method = %Q(
irb(main):002:0"   def hoge
irb(main):003:0"     print __method__
irb(main):004:0"   end
irb(main):005:0"
irb(main):006:0" )
=> "\n  def hoge\n    print __method__\n  end\n\n"
irb(main):007:0> print method

  def hoge
    print __method__
  end

=> nil

% を使ったもの

$ irb
...

irb(main):009:0> method2 = %(ddddd)
=> "ddddd"
irb(main):010:0> method2 = %(ddddd
irb(main):011:0"
irb(main):012:0"
irb(main):013:0" )
=> "ddddd\n\n\n"
irb(main):014:0> print method2
ddddd


=> nil

参考

第18章 Convention over Configuration

2019-07-05

「Rubyによるデザインパターン」の読書メモです。

CoC パターンの主な動機は、設定の負荷を軽減することです。

test.rb

require 'uri'

class Message
  attr_accessor :from, :to, :body

  def initialize(from, to, body)
    @from = from
    @to = URI.parse(to)
    @body = body
  end
end

class MessageGateway
  def initialize
    load_adapters
  end

  def process_message(message)
    adapter = adapter_for(message)
    adapter.send_message(message)
  end

  def adapter_for(message)
    protocol = message.to.scheme
    adapter_class = protocol.capitalize + 'Adapter'
    adapter_class = self.class.const_get(adapter_class)
    adapter_class.new
  end

  def load_adapters
    lib_dir = File.dirname(__FILE__)
    full_pattern = File.join(lib_dir, 'adapter', '*.rb')
    Dir.glob(full_pattern).each {|file| require file }
  end
end

m = Message.new('from', 'ftp://xxxxx?ggg=2', 'bodybody')
mg = MessageGateway.new
mg.process_message(m)

出力結果

$ ruby test.rb
"FtpAdapter:send_message #<Message:0x00007fe951195658>"

注意点

規約に基づいたシステムの構築で危険なことの1つは、利用する規約の出来が不完全な場合、その結果としてシステムにできることの幅を制限するかもしれないということです。

Ruby で itself を使ってみる

2019-07-04

やったこと

Ruby で itself を使ってみます。

確認環境

$ ruby --version
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-darwin17]

調査

itself は self 自身を返却します。

$ irb
irb(main):001:0> string = 'abcd'
=> "abcd"
irb(main):002:0> string.itself
=> "abcd"
irb(main):003:0> array = [3, 2, 1, 3, 2, 1]
=> [3, 2, 1, 3, 2, 1]
irb(main):004:0> array.group_by(&:itself)
=> {3=>[3, 3], 2=>[2, 2], 1=>[1, 1]}

nil とか false で試しても、 self が返却されました。

irb(main):001:0> nil.itself
=> nil
irb(main):002:0> false.itself
=> false

参考

第17章 カスタムオブジェクトを作る: メタプログラミング

2019-07-04

「Rubyによるデザインパターン」の読書メモです。

test.rb

class CompositeBase
  attr_reader :name

  def initialize(name)
    @name = name
  end

  def self.member_of(composite_name)
    code = %Q{
      attr_accessor :parent_#{composite_name}
    }
    class_eval(code)
  end

  def self.composite_of(composite_name)
    member_of composite_name

    code = %Q{
      def sub_#{composite_name}s
        @sub_#{composite_name}s = [] unless @sub_#{composite_name}s
        @sub_#{composite_name}s
      end

      def add_sub_#{composite_name}(child)
        return if sub_#{composite_name}s.include?(child)
        sub_#{composite_name}s << child
        child.parent_#{composite_name} = self
      end

      def delete_sub_#{composite_name}(child)
        return unless sub_#{composite_name}s.include?(child)
        sub_#{composite_name}s.delete(child)
        child.parent_#{composite_name} = nil
      end
    }
    class_eval(code)
  end
end

class Tiger < CompositeBase
  member_of(:population)
  member_of(:classification)
end

class Jungle < CompositeBase
  composite_of(:population)
end

class Species < CompositeBase
  composite_of(:classification)
end

tony_tiger = Tiger.new('tony')
se_jungle = Jungle.new('southeastern jungle tigers')
se_jungle.add_sub_population(tony_tiger)

p tony_tiger.parent_population.class

species = Species.new('P. tigers')
species.add_sub_classification(tony_tiger)
p tony_tiger.parent_classification.class

出力結果

$ ruby test.rb
Jungle
Species

まとめ

メタプログラミングは、必要なコードを取得するためのもっとも簡単な方法になりうるアイディアで、キーボードでコードをすべて書くのではなく、実行時にプログラムに基づいて作り出すというやり方によるものです。

Ruby で %i を使ってみる

2019-07-04

やったこと

%i を使って、シンボルを作ってみます。

確認環境

$ ruby --version
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-darwin17]

調査

%i を使ってシンボルが作れます。

$ irb
irb(main):001:0> %i(a b c)
=> [:a, :b, :c]
irb(main):002:0> %i(a\ b c)
=> [:"a b", :c]

参考

Ruby で collect を使ってみる

2019-07-02

やったこと

collect メソッドを使ってみます。

確認環境

$ ruby --version
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-darwin17]

調査

mapメソッドは、collectの別名です。

$ irb
...

irb(main):002:0> p (1..8).collect {|n| n**3}
[1, 8, 27, 64, 125, 216, 343, 512]
=> [1, 8, 27, 64, 125, 216, 343, 512]
irb(main):003:0> p (1..8).map {|n| n**3}
[1, 8, 27, 64, 125, 216, 343, 512]
=> [1, 8, 27, 64, 125, 216, 343, 512]

collect でも、 map でも同じ結果が返って来てます。

参考

第16章 オリジナル言語を作る: Domain-Specific Languages

2019-07-02

「Rubyによるデザインパターン」の読書メモです。

「内部」DSL

命令文をDSLの中に取り込んで、それをRubyプログラムとして解釈するアイディア

backup.pr

backup '/home/russ/documents'
backup '/home/russ/music', file_name('*.mp3') & file_name('*.wav')
backup '/home/russ/images', except(file_name('*.tmp'))
to '/external_drive/backups'

interval 60

test.rb

require 'finder'
require 'find'

class Expression
  def |(other)
    Or.new(self, other)
  end

  def &(other)
    And.new(self, other)
  end
end

class All < Expression
  def evaluate(dir)
    results = []
    Find.find(dir) do |p|
      next unless File.file?(p)
      results << p
    end
    results
  end
end

class And < Expression
  def initialize(expression1, expression2)
    @expression1 = expression1
    @expression2 = expression2
  end

  def evaluate(dir)
    result1 = @expression1.evaluate(dir)
    result2 = @expression2.evaluate(dir)
    (result1 & result2)
  end
end

class Not < Expression
  def initialize(expression)
    @expression = expression
  end

  def evaluate(dir)
    All.new.evaluate(dir) - @expression.evaluate(dir)
  end
end

class FileName < Expression
  def initialize(pattern)
    @pattern = pattern
  end

  def evaluate(dir)
    results = []
    Find.find(dir) do |p|
      next unless File.file?(p)
      name = File.basename(p)
      results << p if File.fnmatch(@pattern, name)
    end
    results
  end
end

def backup(dir, find_expression=All.new)
  puts "Backup called, source dir=#{dir} find expr=#{find_expression}"
end

def to(backup_directory)
  puts "To called, backup dir=#{backup_directory}"
end

def interval(minutes)
  puts "Interval called, interval = #{minutes} minutes"
end

def except(expression)
  Not.new(expression)
end

def file_name(pattern)
  FileName.new(pattern)
end

eval(File.read('backup.pr'))

まとめ

内部DSLの背景にある考え方は、とても簡単です。Rubyの構文ルールに合うようにあなたがDSLを定義します。そして、DSLで書かれた、やるべきことが記述されているプログラムに必要な基盤もあなたが定義します。

Ruby で defined? を使ってみる

2019-07-01

やったこと

defined? を使ってみます。

確認環境

$ ruby --version
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-darwin17]

調査

$ irb
irb(main):001:0> defined? print
=> "method"
irb(main):002:0> defined? Hoge
=> nil
irb(main):003:0> class Hoge end
=> nil
irb(main):004:0> defined? Hoge
=> "constant"

存在しない Hoge クラスを引数に与えると、nil でしたが、

クラス作成後は constant が返ってきました。

参考

第15章 専用の言語で組み立てる: Interpreter

2019-07-01

「Rubyによるデザインパターン」の読書メモです。

Interpreter の例

require 'find'

class Expression
  def |(other)
    Or.new(self, other)
  end

  def &(other)
    And.new(self, other)
  end
end

class All < Expression
  def evaluate(dir)
    results = []
    Find.find(dir) do |p|
      next unless File.file?(p)
      results << p
    end
    results
  end
end

class FileName < Expression
  def initialize(pattern)
    @pattern = pattern
  end

  def evaluate(dir)
    results = []
    Find.find(dir) do |p|
      next unless File.file?(p)
      name = File.basename(p)
      results << p if File.fnmatch(@pattern, name)
    end
    results
  end
end

class Bigger < Expression
  def initialize(size)
    @size = size
  end

  def evaluate(dir)
    results = []
    Find.find(dir) do |p|
      next unless File.file?(p)
      results << p if (File.size(p) > @size)
    end

    results
  end
end

class Writable < Expression
  def evaluate(dir)
    results = []
    Find.find(dir) do |p|
      next unless File.file?(p)
      results << p if (File.writable?(p))
    end
    results
  end
end

class Not < Expression
  def initialize(expression)
    @expression = expression
  end

  def evaluate(dir)
    All.new.evaluate(dir) - @expression.evaluate(dir)
  end
end

class Or < Expression
  def initialize(expression1, expression2)
    @expression1 = expression1
    @expression2 = expression2
  end

  def evaluate(dir)
    result1 = @expression1.evaluate(dir)
    result2 = @expression2.evaluate(dir)
    (result1 + result2).sort.uniq
  end
end

class And < Expression
  def initialize(expression1, expression2)
    @expression1 = expression1
    @expression2 = expression2
  end

  def evaluate(dir)
    result1 = @expression1.evaluate(dir)
    result2 = @expression2.evaluate(dir)
    (result1 & result2)
  end
end

ins = (Bigger.new(2000) & Not.new(Writable.new)) | FileName.new('*.mp3')
puts ins.evaluate('./')

まとめ

インタープリタを使うと処理速度が遅くなる傾向があり、また高速化することは困難なため、Interpreter パターンの使用は高い性能を必要としない領域に制限するのがよいでしょう。また Interpreter パターンは確実にかなりの基盤を必要とするため、複雑さが生じます。

train_test_split でデータを分割する

2019-06-30

やったこと

訓練データを学習データ、性能評価用のデータに分割します。

確認環境

$ ipython --version
6.1.0
$ jupyter --version
4.3.0
$ python --version
Python 3.6.2 :: Anaconda custom (64-bit)
import sklearn
print(sklearn.__version__)
0.19.0

調査

from sklearn import datasets
iris = datasets.load_iris()
X = iris.data
y = iris.target

from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0)
from sklearn import datasets
X.shape: (150, 4)
y.shape: (150,)
X_train.shape: (120, 4)
X_test.shape: (30, 4)
y_train.shape: (120,)
y_test.shape: (30,)

全体150個のデータのうち、20% の 30個がテストデータになっていることが分かります。

参考