質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.58%

括弧の数を考慮した正規表現について

解決済

回答 5

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 2,470
退会済みユーザー

退会済みユーザー

 聞きたいこと

カッコの数が一致するように正規表現を書きたいです。
[ソード]については問題ないのですが、[]が入れ子になった場合うまく動作しません。

例えば[ソード[炎属性]という出力が得られていますが、[ソード[炎属性]]という出力が欲しいです。
最後のカッコを意識するような正規表現はどのように書けばよいでしょうか?

 ソースコード

main.js

// 無駄な文字列が入っているところから武器名を取り出すプログラム
// aaa, bbb, ccc はダミーの文字列。
let strs = [
  'aaa[ソード]bbb',
  'aaaaa[ソード[炎属性]]bbbb',
  'aaa[ソード][ツインソード]',
  'aaa[ソード[炎属性]]bbb[ツインソード]ccc',
  'aaa[ソード[炎属性]][ツインソード][ソード[水属性]]ccc'
]
let myRegexp = new RegExp(/\[.*?\]/g)
let match = null

while ((match = myRegexp.exec(strs)) !== null) {
  let itemName = match[0]
  console.log(itemName)
}

 実行結果

$ node main.js
[ソード]
[ソード[炎属性]
[ソード]
[ツインソード]
[ソード[炎属性]
[ツインソード]
[ソード[炎属性]
[ツインソード]
[ソード[水属性]

 求めている実行結果

$ node main.js
[ソード]
[ソード[炎属性]]
[ソード]
[ツインソード]
[ソード[炎属性]]
[ツインソード]
[ソード[炎属性]]
[ツインソード]
[ソード[水属性]]

 環境

$ node --version
v8.9.1

 参考

MDN - RegExp.prototype.exec()

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 5

checkベストアンサー

+6

[]の入れ子が一段階までしかなければ/\[(?:[^\[\]]|\[[^\]]*\])*\]/という正規表現で可能です。これは入れ子部分と入れ子でない部分をそれぞれわけて見ているだけとなります。

※ 入れ子が一段階のみの場合はこれで十分です。二段階以上になる場合があれば、続きをお読みください。


先程示した正規表現は入れ子が二段階には対応していません。もし、対応しようとするともっと複雑になりますし、それこそ段階がいくつになるか不明となった場合、(現行仕様の)JavaScript標準の正規表現だけでは不可能です。

括弧の対応が入れ子になっている場合でもマッチ可能にすることは一般的な正規表現ではできません。どうしても正規表現で行いたい場合は部分式呼び出しという機能を持った高度な正規表現を使う必要があります。私が知る限り、この機能を持った正規表現ライブラリはOniguruma(鬼車)(または、その改変版であるOnigmo(鬼雲))しかありません。これらを標準で採用している言語はRubyとPHP(mbstringの一部であるmb_eregの正規表現)のみです。

 どうしても正規表現を使いたい

幸運なことにnpmパッケージとしてonigurumaがあります。これを使えば、Node.jsからOniguromaの正規表現を使うことが可能です。なお、node-gypでコンパイルする必要がありますので、npmでインストールする前にnode-gypの準備を整えておいてください。また、Node.jsでしか使えません。

オリジナル、一段階のみ対応、そしてOniguruma版の三つを用意しましたので、それぞれ実行結果を比べてみてください。サンプルの文字列もいくつか増やしています。

// 無駄な文字列が入っているところから武器名を取り出すプログラム
// aaa, bbb, ccc はダミーの文字列。
import oniguruma from 'oniguruma'
const {OnigRegExp} = oniguruma
let strs = [
  'aaa[ソード]bbb',
  'aaaaa[ソード[炎属性]]bbbb',
  'aaa[ソード][ツインソード]',
  'aaa[ソード[炎属性]]bbb[ツインソード]ccc',
  'aaa[ソード[炎属性]][ツインソード][ソード[水属性]]ccc',
  'aaa[ソード[[闇]属性]][ツインソード][ソード[[光]属性]]ccc',
  'aaa[[エンハンス]ソード[光[[反転]邪]属性]]ccc'
]

{
  let myRegexp = new RegExp(/\[.*?\]/g)
  let match = null

  console.log(`==== ${myRegexp} ====`)
  while ((match = myRegexp.exec(strs)) !== null) {
    let itemName = match[0]
    console.log(itemName)
  }
}
{
  let myRegexp = new RegExp(/\[(?:[^\[\]]|\[[^\]]*\])*\]/g)
  let match = null

  console.log(`==== ${myRegexp} ====`)
  while ((match = myRegexp.exec(strs)) !== null) {
    let itemName = match[0]
    console.log(itemName)
  }
}

{
  let pattern = '(?<paren>\\[(?:\\g<paren>|[^\\[\\]])*\\])'
  let myRegexp = new OnigRegExp(pattern)
  let match = null
  let startPos = 0

  console.log(`==== Oniguruma: ${pattern} ====`)
  while ((match = myRegexp.searchSync(strs, startPos)) !== null) {
    let itemName = match[0].match
    console.log(itemName)
    startPos = match[0].end
  }
}

※ 質問者のコードにあわせて末尾セミコロン無しで書いています。
※ onigurumaパッケージの作りが悪いのか、import {OnigRegExp, OnigScanner} from 'oniguruma'だと--experimental-modulesではエラーになりました。
※ node --experimental-modulesとするか、babelでimportrequireに変換してから実行してください。

 ブラウザの環境でも使いたい

onigurumaパッケージはNode.js上でしか使用できません。ブラウザ上で動作させるには正規表現以外の方法(他の検索やマッチを組み合わせる処理など)も使う必要があります。要望があれば実装を考えます。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/11/04 22:29 編集

    ご回答有り難うございます。
    動作確認が取れ、どちらも期待する動作が得られました。

    入れ子は1段回以上になる事は無く、1段階のみでした。
    お手数をおかけして申し訳ありません。

    回答頂いた2つの正規表現を2時間ほど眺めているとどちらも理解できましたが、自分で書けと言われると時間がかかりそうです…(T_T)
    がんばって慣れていきます。

    部分式呼び出しは初めて聞きました。
    以下Webサイトにいろいろ書かれており使えそうですが、
    自分で書くにはスラスラかけそうにないです…(T_T)
    http://d.hatena.ne.jp/atzy/20080910/p1

    今回はWebブラウザは無視で大丈夫です。
    ありがとうございました。

    キャンセル

  • 2018/11/05 00:16

    > 先程示した正規表現は入れ子が二段階には対応していません。もし、対応しようとするともっと複雑になりますし、それこそ段階がいくつになるか不明となった場合、(現行仕様の)JavaScript標準の正規表現では不可能です。
    厳密には、一度の正規表現のマッチが不可能なだけで、複数回に分けてマッチさせれば、現行仕様でも十分可能です。
    私の回答欄に追記しました。

    キャンセル

  • 2018/11/05 06:48

    >think49さん
    誤解を生まないように「だけ」という表現にしました。これなら正規表現のみを使って、つまり、マッチした文字列の結合などをせずに対象を抜き出すことができないって意味で通じますでしょうか?

    キャンセル

+3

 入れ子回数が1回

入れ子回数が1回限定なら、[] の中で [] を消費するパターンを作れば良いでしょう。

console.log(/\[[^[\]]*(?:\[[^\]]*\][^[\]]*)*\]/g.exec('aaa[ソード[炎属性]][ツインソード][ソード[水属性]]ccc')[0]); // "[ソード[炎属性]]"

 入れ子回数が無制限

入れ子が無制限の場合は、

  • [
  • ]
  • [] 以外の文字列

の3つに分けてマッチさせ、入れ子深度を計測しながら、文字列にマッチさせる必要があります。

matchAllCharacterPair('[a][[b]][[[c]]]', '[', ']');  // ["[a]","[[b]]","[[[c]]]"]

Re: Kyun001 さん

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/11/04 19:38

    ご回答有り難うございます。
    期待する動作を得ることができました。
    しかし、regex visualizer等使っても今の私には呪文にしか見えない為、
    どのように正規表現で表したのかを時間をかけて理解していこうとおもいます。

    キャンセル

+3

そもそも(純粋な)正規表現は正規言語という形式言語を表現するための手段です。正規言語は入れ子構造を表現できません。

なので正規表現では入れ子構造のパースは無理、ということになります。

形式言語 - Wikipedia
正規言語の反復補題 - Wikipedia

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

+1

サンプルを書いてみました。質問には沿っていますが、場合により失敗する例があるので注意してください。その例についても書いています。

<body>
    <textarea id="result" style="height: 300px; width: 500px;"></textarea>
    <script type="text/javascript" language="javascript">
        let strs = [
            'aaa[ソード]bbb',
            'aaaaa[ソード[炎属性]]',
            'aaa[ソード][ツインソード]',
            'aaa[ソード[炎属性]]bbb[ツインソード]ccc',
            'aaa[ソード[炎属性]][ツインソード][ソード[水属性]]ccc',
            'aaa[ソード[炎属性]xx]', // 失敗する例
        ]
        // (?!]): 「後ろが]でない」という表現を追記しました
        let myRegexp = new RegExp(/\[.*?\](?!])/g)
        // matchを使うことで、マッチするパターン全てを得られます
        let results = strs.map(o => o.match(myRegexp))

        document.getElementById('result').value = results
            // 行がもつ要素を結合します
            .map(o => o.join(', '))
            // 行同士を結合します
            .reduce((a, b) => a + '\n' + b)
    </script>
</body>

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/11/04 20:11

    そうすると、失敗する例として挙げた'aaa[ソード[炎属性]xx]'というケースもなさそうですか?なさそうなら、上記の方法でいけるかと。

    キャンセル

  • 2018/11/04 20:15

    すみません、具体例を忘れていました。
    `[ソード[炎属性]レベル2]` のような場合がありましたので、この場合一致しませんでした。

    キャンセル

  • 2018/11/04 20:18

    なるほど。

    キャンセル

0

WWWクリエイターズの「正規表現:最短一致でマッチさせる表現」の記事が参考になると思います。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/11/04 19:32

    回答有り難うございます。
    「?」を使って最短一致になっていた為、最後のカッコが一致していなかったのですね。

    キャンセル

  • 2018/11/04 19:38

    ?を消しちゃうと、最後の]に引っかかるようになっちゃいます。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.58%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る