Python にはリスト(Ruby の配列)のスライスと呼ばれる操作があります。
>>> a = [0, 1, 2, 3] >>> a[0 : 2] [0, 1]
みたいなやつです。Python のお勉強に、これと同等のメソッドを Ruby で実装してみました。Array#slice は既に存在するので、pickup というメソッドにしました。だいぶ苦労しましたねえ。きれいなコードじゃないし。引数が負の場合がややこしかったです。
class Array def pickup(left, right, step=nil) len = self.length unless step return [] 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 = [] left.step(right, step) {|i| selected.push(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
こんな風に使います。引数第三項のステップ数は省略できます(デフォルトは 1)。引数第一項と第二項は(言語仕様上)省略できませんので、代わりに nil を使います。
irb(main):045:0> a = [0, 1, 2, 3, 4, 5] => [0, 1, 2, 3, 4, 5] irb(main):046:0> a.pickup(0, 4) => [0, 1, 2, 3] irb(main):047:0> a.pickup(-4, -1) => [2, 3, 4] irb(main):048:0> a.pickup(2, nil) => [2, 3, 4, 5] irb(main):049:0> a.pickup(nil, -2) => [0, 1, 2, 3] irb(main):050:0> a.pickup(nil, nil) => [0, 1, 2, 3, 4, 5] irb(main):051:0> a.pickup(nil, nil, -1) => [5, 4, 3, 2, 1, 0] irb(main):052:0> a.pickup(-1, 2, -1) => [5, 4, 3] irb(main):053:0> a.pickup(2, nil, 2) => [2, 4]
勉強用なので、エラー処理などはいいかげんです。たぶんだいたいは再現していると思うのですが。Python の対話型シェルも使いながら実装しました。
しかしこれ、移植してみて思ったのですが、引数が負の場合はわかりにくい仕様だと感じました。a.pickup(-1, 2, -1)
の挙動などは、こうする必然性があまり感じられないのですよね。あと、引数が省略された場合(移植メソッドだと nil を入れた場合)、それぞれの端を意味するというのも実装しにくい理由です。自分が慣れているせいですが、Ruby の slice の方がすっきりしているような気もします。
どうも、第二引数が実際のインデックスと一致していないのがわかりにくいです。引数が負の場合など、却ってややこしくなっているのは否めません。
※追記 続編の記事があります。
Python のスライスを Ruby で実装(その2) - Camera Obscura
モジュールの Mix-in を使い、Array 同様 String でも pickup メソッドを使えるようにしました。