結論から言います。
Rubyで複数の文字列を置換するなら、gsub メソッドに Regexp.union とハッシュを組み合わせて渡すべきです。
入門書では gsub を数珠つなぎにする「メソッドチェーン」がよく紹介されますが、置換対象が増えるほど非効率で、何よりメンテナンス性が悪くなります。
Webエンジニアとして、言語の仕様を深く理解し、最短ルートかつ堅牢なコードを書く流儀を身につけてください。
執筆者:kazu |
|
結論:Regexp.unionとハッシュによる一括置換が最短ルート
「AをBに、CをDに置換したい」という場合、多くの人が gsub を連鎖させますが、それはおすすめしません。
文字列のスキャン回数を最小限に抑え、コードを宣言的に保つため
gsub を繋げると、繋げた回数分だけ文字列全体を走査(スキャン)し、その度に新しい文字列オブジェクトを生成します。
短い文字列なら誤差ですが、実務で扱うデータ量や頻度を考えれば、計算リソースを無駄に消費する癖をつけるべきではありません。
また、Rubyの gsub の第2引数にハッシュを渡すと、「マッチした文字列をキーとしてハッシュの値を引き出す(Hash#[])」という挙動をします。
これを利用するのが最もスマートです。
ダメな例:gsub を連鎖させている非効率なコード
# 正直、この書き方は「とりあえず動けばいい」レベルです
text = "Rubyは楽しくて、Rubyは効率的です。"
result = text.gsub("Ruby", "Python").gsub("楽しくて", "難しくて")
puts result
このコードは、1回目の置換で文字列をコピーし、そのコピーに対してまた2回目のスキャンを行っています。
置換対象が10個あれば、10回スキャンとコピーを繰り返すことになります。
非常に無駄です。
良い例:ハッシュとRegexp.unionで一括置換する
# これが実務で選ぶべき「プロの書き方」です
text = "Rubyは楽しくて、Rubyは効率的です。"
replacement_map = {
"Ruby" => "Python",
"楽しくて" => "難しくて"
}
キーを統合して一つの正規表現パターンを作る
pattern = Regexp.union(replacement_map.keys)
1回の走査でマッチしたものをハッシュから引いて置換する
result = text.gsub(pattern, replacement_map)
puts result
この方法の優れている点は、Regexp.union によって検索対象が「一つの正規表現」として最適化される点です。
内部的には、文字列を一度走査する過程でマッチしたものに対し、ハッシュの [] メソッドを呼び出して置換後の文字列を取得しています。
計算量も、エンジニアとしての見た目も、こちらのほうが圧倒的にスマートです。
破壊的メソッド gsub! は「共有オブジェクト」への副作用を意識すべき
「メモリを節約したいから」と、安易に gsub! を使いたがる人がいますが、代償を理解していますか?
意図しない場所でデータが変わるリスクがある
破壊的メソッドは、メモリ上のオブジェクトを直接書き換えます。
もしその文字列が他の変数でも参照(共有)されていた場合、全く関係のない場所でデータが書き換わる「副作用」が発生します。
大規模な開発において、この副作用が原因のバグを追うのは時間の極端なロスです。
良い例:共有の不安があるなら非破壊、閉じたスコープなら破壊的
# 基本はこの「安全な」スタイルが推奨されます
def clean_text(input)
inputそのものを壊さず、新しいオブジェクトを返す
input.gsub(/[!?]/, "")
end
ただし、ループ内で大量の文字列を処理し、GC(ガベージコレクション)を抑えたい、
かつその文字列を他で参照していないことが確実なら、gsub! も有効な手段です。
「原則禁止」とまで極端なことは言いませんが、プロなら「メモリ節約」と「バグのリスク」を天秤にかけ、根拠を持って選択すべきです。
判断がつかないなら、非破壊メソッドを使ってください。
subは「置換する意図」を明確にする時に使い分けるべき
「sub は遅いから全部 gsub でいい」といった極論もまた間違いです。
実務において sub は、「最初の一つだけを変えたい」という仕様上の意図を表現するために重要です。
典型例:URLのプロトコル置換や接頭辞(prefix)の変更
# https化などは、文字列全体を探す必要がないので sub が適切
url = "http://example.com"
secure_url = url.sub("http://", "https://")
パスの先頭だけを書き換えたい場合
path = "/api/v1/resource"
v2_path = path.sub(%r{^/api/v1}, "/api/v2")
これらを gsub で書くと、後から読むエンジニアに「他にも置換される箇所があるのか?」という余計な疑念を抱かせます。
「最初の一箇所のみ」という制約があるなら、sub を使うことでコードに人間味のある「意思」が宿ります。
まとめ:効率的な文字列置換のチェックリスト
Rubyでの文字列置換は、以下のルールで運用してください。
- 複数置換は
Regexp.unionとハッシュを使い、スキャンを最小化する。 gsubの連鎖は、可読性とパフォーマンスの両面で負債になりやすい。- 破壊的メソッド(
!)は、共有オブジェクトへの副作用がないと確信できる時だけ使う。 subは接頭辞の置換など、「一箇所のみ」という意図を明確にするために使い分ける。
無駄のないコードは美しいだけでなく、バグを寄せ付けません。
一つひとつのメソッド呼び出しに根拠を持ち、最短ルートを突き詰めてください。
以上です。



コメント