本題に入る前に
Ruby では Regexp.union を使用すると正規表現のORによる結合を簡単に実現できます。
(ORによる結合には、「union」「和」「選択」「選言」などの呼び方もあります。)
union.rb
a = /[Ff]iz+/
b = /buz+/i
c = Regexp.union(a, b)
p 'fiz'.scan(c) # => ["fiz"]
p 'BUZZZZ'.scan(c) # => ["BUZZZZ"]
p 'FizzBuzz'.scan(c) # => ["Fizz", "Buzz"]
正規表現の連結 (concatenation)
では、あるパターンの後に別のパターンが続くような正規表現はどうでしょうか。
Ruby の正規表現には他の正規表現を埋め込むことができるそうなので、上記はこれにより実現できます。
(このような連結の操作は、「concatenation」「連接」などの呼び方もあります。)
concat.rb
a = /[Ff]iz+/
b = /buz+/i
c = /#{a}#{b}/
p 'Fizz'.scan(c) # => []
p 'buzzFizz'.scan(c) # => []
p 'FizzBUZZ'.scan(c) # => ["FizzBUZZ"]
参考:
二重引用符の文字列リテラルと同様に、正規表現中でも変数展開が可能ですが、与えたものは#to_sで展開されます。
そして、Regexp#to_sでは、「返される文字列は他の正規表現に埋め込んでもその意味が保持されるようになっています」。
余談(AND演算について)
正規表現の基本の演算にはAND演算(正規言語の積集合を取る演算)は含まれておらず、簡単な表記でこれを実現することはできませんが、言語によっては肯定的先読みアサーションで実現できるようです。以下の例では /[Ff]iz+/
と /buz+/i
の両方に部分一致するかどうかを判定しています。
intersection.rb
a = /[Ff]iz+/
b = /buz+/i
c = /\A(?=.*#{a})(?=.*#{b}).*/
p c =~ 'Fizz' # => nil (マッチせず)
p c =~ 'BUZZ' # => nil (マッチせず)
p c =~ 'fizz BUZZ' # => 0 (マッチ)
p c =~ '1 2 Fizz 4 Buzz Fizz' # => 0 (マッチ)
まとめ
# 正規表現の和
union = Regexp.union(a, b)
# または
union = /#{a}|#{b}/
# 正規表現の連接
concat = /#{a}#{b}/
# 正規表現の閉包(0回以上の繰り返し)
closure = /#{a}*/
# 正規表現の積(部分一致)
intersection = /\A(?=.*#{a})(?=.*#{b}).*/
# 正規表現の積(全体一致)
intersection = /(?=\A#{a}\z)(?=\A#{b}\z).*/
おわりに
同じ内容の記事がなさそうだったので書きました。