🔍

Juliaで正規表現を使って文字列が特定の文字を含むか判定して置換する

2022/11/07に公開

「Julia 文字 含む」とか「Julia 文字 含む 判定」をググっても上位にそれらしいページが出てこないのでこの記事を書きました. 正規表現を知らない方はこちらを読んでください. JuliaはPerlと同じ正規表現が使えるようです.

判定する

occursin()を使って, 特定の文字や文字列が含まれるか判定できます. ここでは"x"を含むか判定しています.

julia> occursin("x", "1 + 2*x + x^2")
true

julia> occursin("x", "1 + 2*y + y^2")
false

このr"~"の部分に正規表現を使えます. ここでは整数1文字以上*xという形の文字列を探しています.

julia> occursin(r"\d+\*x", "1 + 2*x + x^2")
true

julia> occursin(r"\d+\*x", "1 + 23*x + x^2")
true

julia> occursin(r"\d+\*x", "1 + 2x + x^2")
false

置換する

replace()を使って文字や文字列を置換できます. 例えば以下のように使います.

julia> replace("1 + 2*x + x^2", "x" => "y")
"1 + 2*y + y^2"

置換でも正規表現を使うことができます. キャプチャグループを使うことで, マッチした部分を取り出して扱うことができます. 例えば(?<coeff>~)の~の部分に好きな正規表現を入れることでき. ここでは数字が1文字以上続く場合にマッチさせ, \g<coeff>で利用しています. 文字を変えたり, 積の記号を変えたり, 係数の部分だけ[]で囲うといった使い方の例を示します.

julia> replace("1 + 2*x + x^2", r"x.*" => "A")
"1 + 2*A"

julia> replace("1 + 2*x + x^2", r"x.*" => s"[\g<0>]")
"1 + 2*[x + x^2]"

julia> replace("1 + 2*x + x^2", r"(?<coeff>\d+)\*x" => s"[\g<coeff>]×y")
"1 + [2]×y + x^2"

パターンマッチング

キャプチャグループを用いた置換はかなり強力ですが, 係数に1を足すとか, 場合分けした処理を書くといった場合はreplace()だけでは難しいので, match()を使ってパターンマッチングの結果を取り出します. この例は@ttabataさんの記事がわかりやすかったです. match()の戻り値に文字列.matchと位置.offsetが使えます.

入力
m = match(r"x", "1 + 2*x + x^2")

if !isnothing(m)     # マッチしないとnothingが戻ります
    @show m.match    # 一致した文字列を取得
    @show m.offset   # 一致した位置を取得
end
出力
m.match = "x"
m.offset = 7

同様にキャプチャグループが使えます.

入力
m = match(r"(?<coeff>\d+)\*x", "1 + 2*x + x^2")

if !isnothing(m)     # マッチしないとnothingが戻ります
    @show m.match    # 一致した文字列を取得
    @show m.offset   # 一致した位置を取得
    @show m[:coeff]  # (?<coeff>\d+)の部分が取り出せます
end
出力
m.match = "2*x"
m.offset = 5
m[:coeff] = "2"

置換したい場合は, 一致した文字列.matchを好きな文字列に置換するだけです.

入力
s = "1 + 2*x + x^2"
m = match(r"x", s)
if !isnothing(m)
    t = replace(s, m.match => "y")
end

@show s
@show t
出力
s = "1 + 2*x + x^2"
t = "1 + 2*y + y^2"

ここではキャプチャグループを使って, 先ほどのreplace()ではできなかった処理を行います. 例えば, 係数に1を足すというものです. まず, 取得したキャプチャm[:coeff]は文字列ですので, parse(Int64, "2")のようにすることで整数に変換できます. これに1を足して, マッチと置換します.

入力
s = "1 + 2*x + x^2"
m = match(r"(?<coeff>\d+)\*x", s)
if !isnothing(m)
    c = parse(Int64, m[:coeff]) + 1 
    t = replace(s, m.match => "$c*x")
end

@show s
@show t
出力
s = "1 + 2*x + x^2"
t = "1 + 3*x + x^2"

まとめてパターンマッチング

match()の代わりにeachmatch()を使うと全てのマッチを取得して配列にしてくれます. キャプチャグループも同様に使え, マッチの一部を変数として取得できます.

入力
s = "1//2 + 2//3*x - 3//1*x^2 + 2//2*x^3"

for m in eachmatch(r"(?<numerator>\d+)//(?<denominator>\d+)", s)
    @show m.match
    @show m[:numerator]
    @show m[:denominator]
end
出力
m.match = "1//2"
m[:numerator] = "1"
m[:denominator] = "2"
m.match = "2//3"
m[:numerator] = "2"
m[:denominator] = "3"
m.match = "3//1"
m[:numerator] = "3"
m[:denominator] = "1"
m.match = "2//2"
m[:numerator] = "2"
m[:denominator] = "2"

応用例

具体例として, Polynomials.jlで生成した有理数係数の多項式をLaTeXで美しく表示したいと思います.

入力
# Pkg.add("Latexify")
# Pkg.add("LaTeXStrings")
# Pkg.add("Polynomials")
using Latexify
using LaTeXStrings
using Polynomials

p = Polynomial([-1//2, -2//3, 3//1, 2//2])
s = "$p"
出力
"-1//2 - 2//3*x + 3//1*x^2 + x^3"

これをLatexify.jlでLaTeXに変換すると以下のようになります. \frac{-1}{2}\frac{3}{1}\cdotが微妙ですね.

入力
latexify(s)

\frac{-1}{2} - \frac{2}{3} \cdot x + \frac{3}{1} \cdot x^{2} + x^{3}

うまくreplace()を使うと以下のようにLaTeXに変形できます.

入力
p = Polynomial([-1//2, -2//3, 3//1, 2//2])
s = "$p"
s = replace(s, "*"=>"")  # *を削除
s = replace(s, r"(?<numerator>\d+)//1" => s"\g<numerator>")                                                 # 分母が1なら分子をそのまま表示
s = replace(s, r"- (?<numerator>\d+)//(?<denominator>\d+)" => s"- \\frac{\g<numerator>}{\g<denominator>}")  # 負の数の場合は分子からマイナスを追い出す
s = replace(s, r"(?<numerator>\d+)//(?<denominator>\d+)" => s"\\frac{\g<numerator>}{\g<denominator>}")      # x//y を \frac{x}{y} に変換

latexstring(s)

-\frac{1}{2} - \frac{2}{3}x + 3x^2 + x^3

以下のように場合分けして, LaTeXに変形することもできます.

入力
p = Polynomial([-1//2, -2//3, 3//1, 2//2])
s = "$p"
s = replace(s, "*"=>"")  # *を削除

for m in eachmatch(r"(?<numerator>\d+)//(?<denominator>\d+)", s)
    if m[:denominator] == "1"
        s = replace(s, m.match => "$(m[:numerator])")                                # 分母が1なら分子をそのまま表示
    elseif occursin("-", m[:numerator])
        s = replace(s, m.match => "- \\frac{$(m[:numerator])}{$(m[:denominator])}")  # 負の数の場合は分子からマイナスを追い出す
    else
        s = replace(s, m.match => "\\frac{$(m[:numerator])}{$(m[:denominator])}")    # x//y を \frac{x}{y} に変換
    end
end

latexstring(s)

-\frac{1}{2} - \frac{2}{3}x + 3x^2 + x^3

参考文献

https://www.tohoho-web.com/ex/regexp.html

https://qiita.com/BlueSilverCat/items/f35f9b03169d0f70818b

https://qiita.com/ttabata/items/8b6e4a2b7c2ca299de03

https://mnru.github.io/julia-doc-ja-v1.0/manual/strings.html#正規表現-1

https://gist.github.com/ohno/eb2ccdf9804d3e869c5fff97ef78ac06

Discussion