電子書籍の厳選無料作品が豊富!

正規表現は苦手なもので、すみませんが教えてください。

下のサンプルで、マッチングに "[(|(].*?[)|)]$"を指定すれば、最後から最短の()内を取得できるものと期待していましたが、実行してみると最短の「(c)」ではなく「(a)x(b)y(c)」がマッチしてしまうようです。

$を除いて"[(|(].*?[)|)]"と"[(|(].*[)|)]"で実行してみると、ちゃんと違いがあるのですが…
$をつけた場合で最短の表記方法は、どのようにすればよいのでしょうか?

Sub sample()
Dim RegE, RegMc
Dim str1 As String, str2 As String

 Set RegE = CreateObject("VBScript.RegExp")
 str1 = "test(a)x(b)y(c)"
 str2 = ""
 RegE.Pattern = "[(|(].*?[)|)]$"

 Set RegMc = RegE.Execute(str1)
 If RegMc.Count > 0 Then
  str2 = RegMc(0).Value
  str1 = RegE.Replace(str1, "")
 End If
 MsgBox (str1 & vbLf & str2)
End Sub

A 回答 (7件)

こんばんは。



こんな感じかな?

>"[(|(].*?[)|)]$" 

VBScript の正規表現には、.*? の「最短マッチ」がなかったと思います。あれば、私は苦労しなかったと思います。それで、タイプライブラリを入れて、BRegExp なんていうものを代用したりするわけですが、他人の環境では強要できません。そうすると、他は、やはり、InstrRev などを使って取り出して使うことになってしまいます。

今回は、仕方がないので、中に入れる文字を特定して、マッチする部分を区分けしなければいけません。

 \w+ の場合は、数字、アルファベット、アンダーバーに限ります。
 [ぁ-龠]+ なら、全角文字です。

 それと、私が書くパターンは、最後尾の$を用いずに、(a) を数えて、その最後を出すという方法を取ります。
以下のコードの部分を変えます。

 Pattern = "[\((]\w+[)\)]" 'パターン
 str2 = RegMc(RegMc.Count - 1) '抽出

'------------------------

Sub sample1()
  Dim RegMc As Variant
  Dim str1 As String
  Dim str2 As String
  str1 = "test(a)x(b)y(c)"
  str2 = ""
  With CreateObject("VBScript.RegExp")
    .Pattern ="[\((]\w+[)\)]$" 'パターン
    .Global = True
    Set RegMc = .Execute(str1)
    If RegMc.Count > 0 Then
      str2 = RegMc(0) '抽出
    End If
    MsgBox (str1 & vbLf & str2)
  End With
End Sub

この回答への補足

回答ありがとうございます。
関係のないつもりでいた、"[(|(]"部分も訂正いただき恐縮です。(やっぱり苦手)

>VBScript の正規表現には、.*? の「最短マッチ」がなかったと思います
最初にそう思って、「最後尾」の$をとってテストして見たところ、「.*」では「(a)e(b)f(c)」が、「.*?」では「(a)」、「(b)」、「(c)」それぞれがマッチするので、わからなくなった次第です。(MS得意の仕様なのかなぁ?)

>最後尾の$を用いずに、(a) を数えて、その最後を出すという方法を
>取ります。
これだと、最後尾ということを別途判定する必要がでてきますね。
でも、いいヒントをいただきましたので、明日、研究してみます。

補足日時:2009/03/23 23:35
    • good
    • 0
この回答へのお礼

研究した結果、なんとかなりそうです。
ありがとうございました。

お礼日時:2009/03/24 11:29

こんにちは。



一応、自分の描いていたものをコード化してみました。

>句読点、「(1)」「々」などの使いそうな文字もはねられてしまうので、範囲を段々広げてみたりもしましたが

カッコの中に、カッコがある場合に、除外するようにしてみました。

Const SRC As String = "(株),(財),(a)" '検索語
Const REP As String = "α,β,γ" '臨時の置換語

このように臨時に置き換えます。参考にしてみてください。もし、分からない部分がありましたら、新たに質問を出してください。


'----------------------------------------------------

Const SRC As String = "(株),(財),(a)" '検索語
Const REP As String = "α,β,γ" '臨時の置換語
'SRC と REP の数は、必ず揃えてください。

Sub Test1()
Dim str1 As String
Dim ret As String
  str1 = "test(a)x(b)y(c(a))"
  ret = rePickUp(str1)
  MsgBox str1 & vbCrLf & ret
End Sub

Function rePickUp(mTxt As String)
  Dim Matches As Object
  Dim rTxt As String
  Dim i As Integer
  mTxt = RepW(mTxt, False)
  With CreateObject("VBScript.RegExp")
    .Pattern = "\([^\)]+\)"
    .Global = True
    Set Matches = .Execute(mTxt)
    i = Matches.Count
    If i > 0 Then
      rTxt = Matches(i - 1)
    End If
  End With
  rTxt = RepW(rTxt, True)
  rePickUp = rTxt
End Function
Function RepW(mTxt As String, Optional flg As Boolean = False)
  Dim i As Integer
  Dim oTxt As String
  Dim ret As String
  Dim wdRep1 As Variant
  Dim wdRep2 As Variant
  wdRep1 = Split(SRC, ",")
  wdRep2 = Split(REP, ",")
  oTxt = Replace(mTxt, ")", ")", 1)
  oTxt = Replace(oTxt, "(", "(", 1)
  If flg = False Then
    For i = 0 To UBound(wdRep1)
      ret = Replace(oTxt, wdRep1(i), wdRep2(i))
    Next i
  Else
    For i = 0 To UBound(wdRep1)
      ret = Replace(oTxt, wdRep2(i), wdRep1(i))
    Next i
  End If
  RepW = ret
End Function
    • good
    • 0
この回答へのお礼

締め切りが早すぎたようで、申し訳ありません。

一時置換ですね。
時々、同様の方法を使わせていただいています。
私の場合は、入力できない文字(ESCなど+連番とか)を利用することが多いですが…

今回は、入力文そのものは入力者の整理方法にまかせている部分が多いので、どのような2重括弧があるのか(ないのか)想定するのが難しいのと、似たような(似ていて違う)マッチングを他にも何種類かやっているので、一つの処理はできるだけ簡単に済ませてしまいたいという気持ちもありました。

全体としてはリトライ可能な処理なので、使用者が「えっ!なんで?」と思うような誤解釈を生じない範囲であれば、完全性はそれほど追求しなくても良いという部分もありました。(より良いに越したことはありませんが)

わざわざ、追加の回答をいただきありがとうございました。

お礼日時:2009/03/26 17:57

こんにちは。



>全角英数や句読点、「(1)」「々」などの使いそうな文字もはねられてしまうので、

#4のレスを読みましたが、本格的に作るとなると、作り直さないといけないように思います。VBAというよりは、正規表現や文字処理も問題です。実際の文字列を見て、パターン自体を考え直さないといけないような気がします。あまり単純な方法ではできないかもしれません。もともと、VBA内は、Unicode ですから、JISのように一括でできるわけではなく、リストが分散しています。

例:"[\u2421-\u2473\u2521-\u2576\uFF66-\uFF9F0-9A-z]+
    • good
    • 0
この回答へのお礼

>VBA内は、Unicode ですから、JISのように一括でできるわけではなく
今まであまり意識してはいませんでしたが、[ぁ-龠]+から範囲を拡張しようとして、どうやらUnicodeらしいことは確認していました。

>例:"[\u2421-\u2473\u2521-\u2576\uFF66-\uFF9F0-9A-z]+
これに似たようなことをやってたのですが、いっそのこと「()以外で」という考えに至ったわけです。(最短の意味では「)以外」が正しいのかも)

いろいろお付き合いいただき、大変ありがとうございました。

お礼日時:2009/03/24 20:08

こんにちは。



もともと最短マッチは、一番先に見つけていたものに対して、最短距離のものを引き出すわけですから、意味が違いますね。今回、(a) (b) (c) の数が決まらないとすれば、しばらく考えてみましたが、以下の方法を考えてみました。ここの掲示板の常連さんたちが考えるパターンのような気がします。(笑)

全角、半角の問題が少し見にくくなりますから、Replace を使ったらどうでしょうか。(これは私が良く使うパターン)いずれにしても、実務的には、最終的には、統一するのではないでしょうか?


"[\((][^\((]+[)\)]"
  ↓
"\([^\)]+\)"

Sub sample2()
  Dim RegMc As Variant
  Dim i As Integer
  Dim str1 As String
  Dim str2 As String
  str1 = "test(a)x(b)y(ccccaa)"  '←全角が入っています
  str1 = Replace(str1, "(", "(", , , 1)
  str1 = Replace(str1, ")", ")", , , 1)
  str2 = ""
  With CreateObject("VBScript.RegExp")
    .Pattern = "\([^\)]+\)"
    .Global = True
    Set RegMc = .Execute(str1)
    i = RegMc.Count
    If i > 0 Then
      str2 = RegMc(i - 1)
    End If
    MsgBox (str1 & vbCrLf & str2)
  End With
End Sub


正規表現を使わない方法
(私が実際に書く方法です。RegExp は、オブジェクトを最初に作っておくか、参照設定するなら良いのですが、インスタンスが生成されますから、もし、短い期間なら、VB関数で処理してしまいます。ネット検索では、こちらの方が速いようです。複雑なものには向いていません。)

Sub sample3()
  Dim RegMc As Variant
  Dim i As Integer
  Dim j As Integer
  Dim str1 As String
  Dim str2 As String
  str1 = "test(a)x(b)y(ccccaa)" '←全角が入っています
  str2 = ""
  i = InStrRev(str1, "(", , 1) '1...TextCompare
  j = InStr(i, str1, ")", 1)
  If i > 0 And j > 0 Then
   str2 = Mid$(str1, i, j - i + 1)
  End If
  MsgBox str1 & vbCrLf & str2
End Sub

私は、この種のマクロが一番多いのに、少しも覚えないですね。(^^;Perlの勉強を辞めてしまったからですが。
    • good
    • 0
この回答へのお礼

>半角の問題が少し見にくくなりますから、Replace を使ったらどうでしょうか。
これは処理が簡単になることもあり、頭の片隅にはありましたが、入力者のミスの場合はOKですが、意図的に使い分けている可能性も考慮すると、使わないですむならそれに越したことはないと考えていました。

>実務的には、最終的には、統一するのではないでしょうか?
ん~、そうですね。 「行末に(…)がある場合は、○○の意味とする」などのルールも同時に決めているので、ルールの方で半角( )のみに限定してしまうとかするほうがいいのかなと考えているところです。
(あくまでも、入力文は尊重してあげようと…)

>"\([^\)]+\)"
「最短」の意味からは、[ ]内は)のみでOKなのですが、$を付けた場合、パターンの先頭からが優先されるのか、後ろからが優先されるのかよくわからなかったので… ( 実験してみろって? すんません。)
まぁ、当初は考慮していなかった2重括弧の場合なども考えてしまったりしたこともあるのですが。(当初の質問の最短とはズレてきてますが…)

>正規表現を使わない方法
なるほど。 プリミティブですが正規表現などで悩まずにすみますね。(笑)
今回は同じ文章に対して、何種類かのマッチングでテストして、文章の構成と解釈を決めていることもあったので、はなからパターンだけで処理できる正規表現に走っていました。
速度も速いとのことですので、覚えておきます。

いろいろ、追加情報をありがとうございました。

お礼日時:2009/03/24 20:02

#3 の補足の部分


>関係のないつもりでいた、"[(|(]"部分も訂正いただき恐縮です。
\( も ( も両方とも行けるようでしたが、なんとなく、違っていたような気がしました。 | は、その左右が、二文字以上で、文字列を( ) で括った場合に、その文字列どちらかになる、ということだったと思います。この場合は、どちらでも同じです。

VBScript の正規表現は、簡単なようでも、逆に、標準ではありません。Perl 標準のものを出してほしいものですね。

他は、また、見てみます。

この回答への補足

お礼を書いちゃったので、補足に追加ですが、
おまじない程度に2重括弧までを考慮して、こんなところかなと考えています。
 "[\((](([\((][^\(\)()]+[)\)])|[^\(\)()])+[)\)]$"

補足日時:2009/03/24 12:05
    • good
    • 0
この回答へのお礼

補足への解説まで、わざわざありがとうございます。

>| は、その左右が、二文字以上で、文字列を( ) で括った場合に、
>その文字列どちらかになる
うっかり混同して、[ ]の中で使ってしまってました。
そこまで見通して訂正していただいていたので、恐縮した次第です。

対象が自由入力文字列なので、入力可能な文字は極力そのまま通したいというのがやっかいなところです。(事前に「(」→「(」や半角英数などへの変換をしておけば、少しは簡単になるのですが…)
おまけに、似たようなマッチングを数種類行っているので。

さて、教えて頂いた[ぁ-龠]+だと全角英数や句読点、「(1)」「々」などの使いそうな文字もはねられてしまうので、範囲を段々広げてみたりもしましたが、最終的に、()以外の文字の繰り返しを許すということで、
"[\((][^\(\)()]+[)\)]$" というパターンでどうやらうまくいきそうです。(最後尾判定もできているみたい)
これでも、()内に(株)みたいなのが入っていると、はねてしまうのですが、まぁしかたがないかと。(これ以上は、私には荷が重いので)

一時は、"[)\)]$"にマッチした場合で、"[\((].*?[)\)]"にマッチする最終のものという2段階方式でもしかたないかと思っていたのですが、なんとかなりそうです。
いろいろとお知恵を、ありがとうございました。

お礼日時:2009/03/24 11:29

#1です。



もしかして
str1 = "test(a)x(b)y(c)abcd"
でも最後尾の(c)を取得したいなら、

Sub sample()
Dim RegE, RegMc
Dim str1 As String, str2 As String

Set RegE = CreateObject("VBScript.RegExp")

str1 = "test(a)x(b)y(c)abcd"
str2 = ""
RegE.Pattern = "\(.?\)"
RegE.Global = True

Set RegMc = RegE.Execute(str1)

MsgBox RegMc.Item(RegMc.Count - 1)

End Sub

こんな感じとか?
    • good
    • 0
この回答へのお礼

いろいろ気を回していただき、大変恐れ入ります。

今回は、このケースではありません。 >最後尾
No1の回答のケースでOKなんですが…

お礼日時:2009/03/23 23:13

最短とは最後尾で良いのでしょうか?



Sub sample()
Dim RegE, RegMc
Dim str1 As String, str2 As String

Set RegE = CreateObject("VBScript.RegExp")
str1 = "test(a)x(b)y(c)"
str2 = ""
RegE.Pattern = "\(.?\)$"

MsgBox RegE.Execute(str1)(0)

Stop

Set RegMc = RegE.Execute(str1)
If RegMc.Count > 0 Then
str2 = RegMc(0).Value
str1 = RegE.Replace(str1, "")
End If
MsgBox (str1 & vbLf & str2)
End Sub

こうゆう事とは違いますか?
    • good
    • 0
この回答へのお礼

回答ありがとうございます。

>最後尾で良いのでしょうか?
はい。最後尾のつもりです。

確かに、「.?」だと「(c)」はマッチするのですが、()内に複数文字列があるので(例では1文字でしたが…)、「(cd)」がマッチしなくなるため、「.*?」としていました。
でも、No3様の情報を見ると、どうやらダメみたいですね…

お礼日時:2009/03/23 23:11

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!