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

nekoTheShadow’s diary

技術ブログとして始めたはずが、読書&愚痴ブログになりました(´・ω・`)

rubyの不満をremoveしたい。

ruby

rubyは素敵なプログラミング言語だと思いますが、不満に感じないところがないかというと……

個人的に一番どうにかならないかと思うのは、Arrayのクラスメソッドである

  • delete
  • delete_at
  • delete_if

の仕様ですね。

めんどくさいので、仮にこの3種をdelete系と呼んでおきます。
さて、このdelete系、総じて返り値が削除したものなのです。
具体的に言うと、次のような感じ。

ary = [1,2,3]
p ary.delete(1) #=> 1
p ary #=> [2,3]

不満点はいくつかあって

  • Arrayが返ってこないので、Arrayのクラスメソッドをつなげられない
  • 破壊的な動作が気に入らない
    • 破壊的ならせめてdelete!としたい

あたりですかね。

いろいろ事情があってこうなっているのはわかるのですが、個人的には非常にストレスがたまるので、新しくメソッドを定義してしまいましょう。
仕様書はこちら。

  • メソッド名は"remove"を利用する
    • delete => remove
    • delete_at => remove_at
    • delete_if => remove_if
  • 非破壊的
    • 破壊的なものは今回定義しない。
      • 個人的にあまり使わないため
      • 面倒なため
  • 基本的にはdeleteと同じような使い方ができるようにする

remove

ドキュメント曰く

指定された val と == で等しい要素を自身からすべて取り除きます。 等しい要素が見つかった場合は最後に見つかった要素を、 そうでない場合には nil を返します。
ブロックが与えられた場合、val と等しい要素が見つからなかったときにブロックを評価してその結果を返します。

とのこと。
つまりブロックが渡されたときとそうでないときを分ける必要がありますね。

class Array    
  def remove(val)
    # ブロックが渡されており、かつvalと等しい要素が見つからなかった場合
    return yield if block_given? && !self.include?(val)
     
    temp = self.dup
    temp.delete(val)
    temp
  end
end

ary = [1,2,3]
p ary.remove(1) #=> [2,3]
p ary.remove(1){"not_found"} #=> [2,3]
p ary.remove(4){"not_found"} #=> "not_found" 

remove_at

こちらはドキュメント曰く

指定された位置 pos にある要素を取り除きそれを返します。 pos が範囲外であったら nil を返します。

とのことのなので、場合分けはいらなさそう。
ただしposが範囲外だった場合、remove_atではselfを返すようにします。

class Array
  def remove_at(pos)
    temp = self.dup
    temp.delete_at(pos)
    temp
  end
end

ary = [1,2,3]
p ary.remove_at(0) #=> [2,3] 
p ary.remove_at(9) #=> [1,2,3] 

remove_if

またまたドキュメントを引用しましょう。

要素を順番にブロックに渡して評価し、その結果が真になった要素をすべて削除します。 delete_if は常に self を返しますが、reject! は要素が 1 つ以上削除されれば self を、 1 つも削除されなければ nil を返します。


ブロックが与えられなかった場合は、自身と reject! から生成した Enumerator オブジェクトを返します。 返された Enumerator オブジェクトの each メソッドには、 もとの配列に対して副作用があることに注意してください。

ここで模倣したいのはrejectではなく、delete_ifなので、remove_ifは次のようになるはずです。

  • ブロックがある場合、要素を順番に評価して、真になったものを削除。
    • 返り値はもちろんArray
    • 何も削除しなければ、selfを返す。
  • ブロックが与えられなかった場合、Enumeratorを返す。
class Array
  def remove_if
    temp = self.dup
    # ブロックを持たない場合
    return temp.delete_if unless block_given?
        
    self.each{|val| temp.delete(val) if yield(val)}
    temp
  end  
end

nums = (1..10).to_a
# 奇数だけ削除する
p nums.remove_if{|num| num % 2 == 0} #=> [1,3,5,7,9]

removed_nums =  nums.remove_if
p removed_nums.class #=> Enumerator
p removed_nums.each{|i| i % 2 == 0} #=> [1,3,5,7,9]

以上をまとめたものが以下になります。

なかなかうまく言っているのではないかしら。