読者です 読者をやめる 読者になる 読者になる

Python のスライスを Ruby で実装(その2)

Pythonのリストのスライスに同等のメソッドをRubyで実装 - Camera Obscura
上の記事では Python のスライスを RubyArray に対して実装しましたが、ほんの少しの修正で完全に String にも適用できます。いわゆる「ダック・タイピング」ですね。なので、モジュール化して Array と String に include させました。モジュールのいわゆる Mix-in です。

module Pyrb
  def pickup(left, right, step=nil)
    klass = self.class
    len = self.length
    unless step
      return klass.new unless !right or right != 0
      left ||= 0
      right ||= 0
      right -= 1
      self[left..right]
    else
      if step > 0
        left ||= 0
        right ||= len
        left = convp(left, len)
        right = convp(right, len)
        right = len if right >= len
        right -= 1
      elsif step < 0
        left ||= len - 1
        left = len - 1 if left >= len
        right ||= - len - 1
        left = convm(left, len)
        right = convm(right, len)
        right = - len - 1 if right < - len - 1
        right += 1
      else
        raise "ValueError: slice step cannot be zero"
      end
      selected = klass.new
      left.step(right, step) {|i| selected << self[i]}
      selected
    end
  end
  
  def convp(i, len)
    return i if i >= 0
    i + len
  end
  
  def convm(i, len)
    return i if i < 0
    i - len
  end
  private :convp, :convm
end  

Array.send(:include, Pyrb)
String.send(:include, Pyrb)

実際にうまくいきました。pickup メソッドが、Array と String でまったく同様に実行されています。ポリモーフィズム

irb(main):050:0> a = "327865"
=> "327865"
irb(main):051:0> a.pickup(0, 4)
=> "3278"
irb(main):052:0> a.pickup(-1, -4, -1)
=> "568"
irb(main):053:0> a.pickup(2, nil)
=> "7865"
irb(main):054:0> b = [3, 2, 7, 8, 6, 5]
=> [3, 2, 7, 8, 6, 5]
irb(main):055:0> b.pickup(0, 4)
=> [3, 2, 7, 8]
irb(main):056:0> b.pickup(-1, -4, -1)
=> [5, 6, 8]
irb(main):057:0> b.pickup(2, nil)
=> [7, 8, 6, 5]
irb(main):058:0> b.pickup(1, 0)
=> []
irb(main):059:0> a.pickup(1, 0)
=> ""
irb(main):060:0> a.pickup(nil, nil)
=> "327865"
irb(main):061:0> b.pickup(nil, nil)
=> [3, 2, 7, 8, 6, 5]