This is IT

技術、日常

Arrayクラスのdelete_ifメソッドで、正規表現にマッチする文字列を削除できない

概要

タイトル通り、delete_ifメソッドでとある正規表現オブジェクトにマッチする文字列を削除できなかったお話。

w = %w(a b c d).delete_if{|s| s == /b+/}

p w
=> ["a", "b", "c", "d"]

ならばdeleteではどうだと試みる。

w = %w(a b c d).delete(/b+/)

p w
=> nil

全部消えただと・・・。 ちょっと訳が分からないゾ。

deleteは、削除した要素を戻り値にする

公式リファレンスを見てみると、

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

なるほど。つまりは、

× 引数に渡した要素を削除した配列を返す

〇 引数に渡した要素を削除して、その要素をそのまま返す

ということ。

更に言うと、deleteメソッドでは(多分)正規表現を引数に使えないので、今回の場合は要素が何も見つからずnilが戻り値としてそのまま返ってきてしまったみたい。

正規表現では==ではなく、===を使う

今回の本題。 delete_ifメソッドが悪いのではなく、どうやらブロックの中が悪かった。 理屈はともかく、正規表現オブジェクトとの一致を条件式に書く際は、===を利用する。

しかしなぜだかdelete_ifメソッドでは===を使っても上手くマッチをしてくれない。 どうすればよいのだろうか?

grepメソッドか、grep_vメソッドを使おう

公式リファレンスを見ると、grepメソッドは

pattern === item が成立する要素を全て含んだ配列を返します。

grep_vメソッドは、

Enumerable#grep のマッチの条件を逆にして、pattern === item が成立しない要素を全て含んだ配列を返します。

まさに今回探していたメソッド。このpatternというのが正規表現オブジェクト、itemがブロックパラメータと思われる。

w = ['a', 'b', 'c'].grep(/b+/)
p w 
=>  ["b"]


z = ['a', 'b', 'c'].grep_v(/b+/)
p z
=>  ["a", "c"]

。 delete_ifメソッドの謎はさておき、今回はこれを使っていこう。