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

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

新規登録して質問してみよう
ただいま回答率
85.46%
PHP

PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。

正規表現

正規表現とは特定の文字列によるパターンマッチングを行う際に用いられる宣言型プログラミングです。

Q&A

解決済

1回答

1201閲覧

[正規表現]先頭に先読み設置の意味

Fujiman

総合スコア41

PHP

PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。

正規表現

正規表現とは特定の文字列によるパターンマッチングを行う際に用いられる宣言型プログラミングです。

0グッド

2クリップ

投稿2021/12/22 04:22

編集2021/12/23 02:59

[概要]

こちらの質問のベストアンサーを理解しようとしています。
意味としてどうしても解釈できない部分があり、教えて下さい。

[分からないコード]

PHP

1 /^(?=[^「」\p{C}\p{L}\p{N}]*+[\p{L}\p{N}])$/

[自分(なり)の解釈]

カッコと制御文字等とアルファベットと数字でない文字が
1文字以上続き、かつ、
アルファベット数字が後ろに続いてる文字列の前にある候補

しかしこの解釈では本質的な意味をなさない

[分からない点]

  • 先頭に先読みを置く意味
  • 否定候補と候補の両方に\p{L}\p{N}が含まれている

 (この先読みの意味する所全般が理解できない)

  • 候補の間にあるゼロ回以上、1回以上の記号が連続している意味

この該当部分のところのコメントに「\p{L}\p{N} を最低1個は含む」とありますが
それならば「(?=[\p{L}\p{N}]+?)」ではだめなのでしょうか。

[試してみたこと]

① Unicode 文字プロパティの確認
C:コントロール文字や非可視整形用文字、サロゲート等
L:アルファベット(Ll、 Lm、Lo、Lt および Lu を含む)
N:数字

② 該当部分を削除してその部分の働きを見る
(2箇所の該当部分を削除して実行する)

PHP

1function parse(string $input): array{ 2 static $pattern = <<<'EOD' 3/ 4 # 先頭 5 ^ 6 # name 部分 7 (?<name> 8 # \p{L}\p{N} を最低1個は含む 9       # <<<削除>>> 10 # カッコと制御文字以外の繰り返し 11 [^「」\p{C}]++ 12 )? 13 # カッコ+空白だけのコメントの場合は読み飛ばす条件分岐 14 (?: 15 # empty_comment 部分 16 (?<empty_comment> 1718 # カッコの中身 19 (?: 20 # empty_comment の再帰 21 (?&empty_comment) 22 )*+ 2324 ) 25 # empty_comment がマッチしたらそこまでをすべて読み飛ばす 26 (*SKIP)(*FAIL) 27 | 28 # comment 部分 29 (?<comment> 3031 # カッコの中身 32 (?: 33 # \p{L}\p{N} を最低1個は含む 34               # <<<削除>>> 35 # カッコと制御文字以外の繰り返し 36 [^「」\p{C}]*+ 37 38 # または 39 | 40 # comment の再帰 41 (?&comment) 42 )*+ 4344 )? 45 ) 46 # 末尾 47 $ 48 /ux 49EOD; 50 51 preg_match($pattern, $input, $match); 52 $name = $match['name'] ?? ''; 53 $comment = $match['comment'] ?? ''; 54 // 外側のカッコを切り取り 55 $comment = mb_substr($comment, 1, -1, 'UTF-8'); 56 $name = $name === '' ? null : $name; 57 $comment = $comment === '' ? null : $comment; 58 return compact('name', 'comment'); 59} 60 61$res = parse('二郎「男の中の「男」です」'); 62var_dump($res); 63 64/* ②箇所とも削除した結果 65  ["name"]=>null 66 ["comment"]=>null 67*/ 68 69/* 本来の(削除前の)結果 70  ["name"]=> string(6) "二郎" 71 ["comment"]=> string(27) "男の中の「男」です" 72*/ 73

<name>のグループ化の方の該当部分だけを削除して実行する(正常動作する)

PHP

1/* <name>の方の該当部分だけ削除して得られた結果 2  ["name"]=> string(6) "二郎" 3 ["comment"]=> string(27) "男の中の「男」です" 4*/

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

前回の回答でも貼りましたが,

言語別:パスワード向けの正規表現 - Qiita

この部分の考え方ですよね。

// 半角英数字をそれぞれ1種類以上含む8文字以上100文字以下の正規表現 /^(?=.*?[a-z])(?=.*?\d)[a-z\d]{8,100}$/i

↑について,以下の具体例で考えてみましょう。

abcdefgh

  1. 先頭の ^ がマッチ。直後に (?=...) の肯定先読みの言明があるため, ^ に対する制約として機能する。^ は先頭にしか存在しないので1回限りのチャンス。
  2. .*? はマッチ無し, [a-z]a がマッチ。
  3. 続いて,先頭に対して同様に (?=.*?\d) の言明の制約を適用する。
  4. 数字が含まれていないので,一切これはマッチしない。よって言明が失敗する。
  5. 1文字オフセットを奥にずらしてもう1回最初から試みようとするが, ^ があるため,ここ以降はすべて失敗することが確定。

abcd12xy

  1. 先頭の ^ がマッチ。直後に (?=...) の肯定先読みの言明があるため, ^ に対する制約として機能する。^ は先頭にしか存在しないので1回限りのチャンス。
  2. .*? はマッチ無し, [a-z]a がマッチ。
  3. 続いて,先頭に対して同様に (?=.*?\d) の言明の制約を適用する。
  4. .*? がマッチ無しの場合, \da はマッチしないので失敗。次に長い候補を試す。
  5. .*?a にマッチする場合, \db はマッチしないので失敗。次に長い候補を試す。
  6. .*?ab にマッチする場合, \dd はマッチしないので失敗。次に長い候補を試す。
  7. .*?abc にマッチする場合, \dd はマッチしないので失敗。次に長い候補を試す。
  8. .*?abcd にマッチ, \d1 がマッチ。
  9. ^ に対する制約条件をクリアしたので,ここから (先読みではない) [a-z\d]{8,100}$ に対するマッチが始まり, abcd12xy がマッチして完了。

先頭から検査して「最低〜が含まれていればいい」という条件なので, 初めて目標を見つけるまでの, *? の最短マッチでいいことが分かります。最長マッチ * を任意文字 . に対して使った場合, 末尾まですべて消化し,後ろから1文字ずつ消していく流れになるので無駄が多いです。

今回の先頭部分を簡略化した /^(?=[^「」\p{C}\p{L}\p{N}]*+[\p{L}\p{N}])[^「」\p{C}]++「/u についても説明します。少し工夫を加えている点があるので,以下に補足をします。

  • 文字種を限定しない場合は *? にして最短マッチにしましたが,ここでは文字種を後続のグループに含まれないものに限定しているため* で最長マッチにすることができます。更に *+ として独占量指定子を付与してバックトラッキングを禁止することにより,**「一番長いパターンだけチェックして,それでマッチしなかったらそれよりも短いパターンは一切試さずに失敗する」**という最適化を入れています。

太郎「

  1. 先頭の ^ がマッチ。直後に (?=...) の肯定先読みの言明があるため, ^ に対する制約として機能する。^ は先頭にしか存在しないので1回限りのチャンス。
  2. [^「」\p{C}\p{L}\p{N}]*+ がマッチ無し, [\p{L}\p{N}] がマッチ。
  3. ^ に対する制約条件をクリアしたので,ここから (先読みではない) [^「」\p{C}]++「 に対するマッチが始まり, 太郎「 がマッチして完了。

太郎

  1. 先頭の ^ がマッチ。直後に (?=...) の肯定先読みの言明があるため, ^ に対する制約として機能する。^ は先頭にしか存在しないので1回限りのチャンス。
  2. [^「」\p{C}\p{L}\p{N}]*+ がマッチ無し, [\p{L}\p{N}] がマッチ。
  3. ^ に対する制約条件をクリアしたので,ここから (先読みではない) [^「」\p{C}]++「 に対するマッチが始まるが, 太郎 だけではマッチしないので失敗。

†漆黒の堕天使†「

  1. 先頭の ^ がマッチ。直後に (?=...) の肯定先読みの言明があるため, ^ に対する制約として機能する。^ は先頭にしか存在しないので1回限りのチャンス。
  2. [^「」\p{C}\p{L}\p{N}]*+ がマッチ, [\p{L}\p{N}] がマッチ。
  3. ^ に対する制約条件をクリアしたので,ここから (先読みではない) [^「」\p{C}]++「 に対するマッチが始まり, †漆黒の堕天使†「 がマッチして完了。

††「

  1. 先頭の ^ がマッチ。直後に (?=...) の肯定先読みの言明があるため, ^ に対する制約として機能する。^ は先頭にしか存在しないので1回限りのチャンス。
  2. [^「」\p{C}\p{L}\p{N}]*+†† がマッチするが, [\p{L}\p{N}] にマッチするものが無い。よって言明が失敗する。
  3. 1文字オフセットを奥にずらしてもう1回最初から試みようとするが, ^ があるため,ここ以降はすべて失敗することが確定。

それならば「(?=[\p{L}\p{N}]+?)」ではだめなのでしょうか。

もうおわかりだと思いますが,この正規表現は名前部分に \p{L}\p{N} に含まれない記号を許可しつつ,それだけの構成は禁止するという形になっています。要件として,名前がすべて \p{L}\p{N} である場合はもちろん先読み無しで実現できる範囲のものにはなるでしょう。

追記1

最短マッチ (Reluctant)最長マッチ (Greedy)独占的最長マッチ (Possessive)
????+
*?**+
+?+++
{3, 8}?{3,8}{3,8}+
{5,}?{5,}{5,}+

上記のフォーマットは以下のようになっています。

<回数を表す表記><マッチの積極性を表す表記>

どちらにも ? + が登場していますが,これらは全く異なる意味を持つので注意が必要です。

回数を表す表記意味
?0 回または 1回
*0 回以上
+1 回以上
{3,8}3回〜8回
{5,}5回以上
マッチの積極性を表す表記意味
?最もマッチ量が少ないものから考える。条件が満たされなかったとき,1文字増やして次の可能性を確認する。
無し最もマッチ量が多いものから考える。条件が満たされなかったとき,1文字減らして次の可能性を確認する。
+最もマッチ量が多いものだけを考える。条件が満たされなかったとき,即座に失敗扱いにする。

投稿2021/12/22 06:20

編集2021/12/22 09:36
mpyw

総合スコア5223

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Fujiman

2021/12/22 08:20 編集

詳細な解説ありがとうございます。まだ理解は不十分ですが現時点で理解したことをまとめてみました。最も知りたかったのは先頭の先読み設置の意味とアスターリスクとプラスの意味でした。 ・先頭に先読みを置く意味は先頭の制約であり、シーケンシャルに複数の先読みを設置できる ・先頭の制約であるため、そのパターンマッチに失敗した時点で処理が終わる ・先頭の制約がクリアできれば後続のパターンマッチに移行する ・否定候補と候補の間にあるアスターリスクとプラス部分は最長一致に限定するため ・否定候補と候補の両方に\p{L}\p{N}があるのは、 \p{L}\p{N}に含まれていない記号は受け入れるが、 最低でも\p{L}か\p{N}は1文字以上含んでるという条件を 満たしてる先頭でないといけないという意味 だからグループnameがすべて \p{L}\p{N} であることが約束されてるならば このような先読みはなくてもいい ただ、質問の質問で大変申し訳ないのです示していただいたサンプルの 肯定先読み内で「.*?」とあるのですが、このパターンは始めてみました。 .*はよく使うと思うのですが、その後ろに?をおくとどういった意味になりますか? 任意の文字がゼロ回以上繰り返しているものに?を付けることで そのパターンが0個か1個の場合にマッチするという意味になると解釈すると ’abcdefgh’ はマッチすると思うのですが、マッチなしとなるとなってます。 実際に動かしてみても確かにマッチしませんでした。 かなり基本的なことだと思うのですが教えていただけますでしょうか よろしくおねがいします。
mpyw

2021/12/22 09:17

前半部分,完璧に合ってます???? >> 肯定先読み内で「.*?」とあるのですが、このパターンは始めてみました。 >> .*はよく使うと思うのですが、その後ろに?をおくとどういった意味になりますか? 回答に表を追記しました。
Fujiman

2021/12/22 10:57 編集

早速、解説ありがとうございます。 また詳細な追記ありがとうございます。 私にとってはこのフォーマとはとてもありがたい情報です。 <回数を表す表記><マッチの積極性を表す表記> おそらく今までにも何度か出会っているはずですが 深く掘り下げずに、なんとなくの解釈だけだったと思います。 特に「独占的最長マッチ」は元の質問で問題になってた 「」が入れ子状態の場合の一番外側を基準にしたいとき これなくして実現するのはほぼ無理なのではとさえ思える貴重なものでした。 もとの質問と私の質問に答えていただいた情報は 私にはとても有益なもので、自力で到達するのが 難しそうなものばかりでした。ありがとうございました
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.46%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問