18

これは、一連の教育正規表現記事の4番目の部分です。ネストされた参照(この正規表現は三角数をどのように見つけるか?)とアサーション内の「カウント」(参照:a ^ nb ^nをJava正規表現と一致させる方法)の組み合わせを使用して文字列を反転する方法を示します。 。プログラムで生成されたパターンは、メタパターンの抽象化を使用します(このJava正規表現はパリンドロームをどのように検出しますか?を参照してください)。シリーズで初めて、これらの手法は、文字列全体の照合ではなく、置換に使用されます。

完全に機能するJavaおよびC#の実装が提供されます。心に強く訴える引用が含まれています。

正規表現を使用して文字列を逆にすることは、決して良い考えとは思えませんでした。また、それが可能かどうか、もしそうなら、どのようにそうしようとするかはすぐにはわかりませんでした。

それはまだ良い考えではありませんが、少なくとも今ではそれが可能であることがわかっています。これを行う1つの方法があります。

C#ideone.comにもあります

using System;
using System.Text.RegularExpressions;

public class TwoDollarReversal {    
public static void Main() {
   string REVERSE = 
      @"(?sx) . grab$2"
         .Replace("grab$2",
            ForEachDotBehind(
               AssertSuffix(@"((.) \1?)")
            )
         );
   Console.WriteLine(
      Regex.Replace(
         @"nietsniE treblA --
         hguone llew ti dnatsrednu t'nod uoy ,ylpmis ti nialpxe t'nac uoy fI",

         REVERSE, "$2"
      )
   );
   // If you can't explain it simply, you don't understand it well enough
   // -- Albert Einstein
}      
// performs an assertion for each dot behind current position
static string ForEachDotBehind(string assertion) {
   return "(?<=(?:.assertion)*)".Replace("assertion", assertion);
}
// asserts that the suffix of the string matches a given pattern
static string AssertSuffix(string pattern) {
   return "(?=.*$(?<=pattern))".Replace("pattern", pattern);
}

}

Java ideone.comにもあります

class TwoDollarReversal {

public static void main(String[] args) {
   String REVERSE =
      "(?sx) . grab$2"
         .replace("grab$2",
            forEachDotBehind(
               assertSuffix("((.) \\1?)")
            )
         );

   System.out.println(
      "taerG eht rednaxelA --\nyrt lliw ohw mih ot elbissopmi gnihton si erehT"
         .replaceAll(REVERSE, "$2")
   );
   // There is nothing impossible to him who will try
   // -- Alexander the Great"
}

static String forEachDotBehind(String assertion) {
   return "(?<=^(?:.assertion)*?)".replace("assertion", assertion);
}
static String assertSuffix(String pattern) {
   return "(?<=(?=^.*?pattern$).*)".replace("pattern", pattern);
}

}

C#バージョンとJavaバージョンはどちらも同じ全体的なアルゴリズムを使用しているようですが、抽象化された実装の詳細にわずかな違いがあります。

明らかに、これは文字列を逆にするための最良の、最も簡単で、最も効率的な方法ではありません。とはいえ、正規表現について学ぶために。パターンを概念化する方法。それらに一致するようにエンジンがどのように機能するか。さまざまなパーツを組み合わせて、必要なものを構築する方法。読みやすく、保守しやすい方法でこれを行う方法。そして、何か新しいことを学ぶという純粋な喜びのために、これがどのように機能するかについて説明してもらえますか?


付録:チートシート!

これは、使用される基本的な正規表現構造の簡単な説明です。

  • (?sx)埋め込まれたフラグ修飾子です。s「単一行」モードを有効にして、ドットを任意の文字(改行を含む)に一致させます。エスケープされていない空白が無視される(コメントに使用できる)フリースペースxモードを有効にします。#
  • ^および$は、行の先頭と末尾のアンカーです。
  • ?繰り返し指定子はオプション(つまり、0または1)を示します。たとえば、繰り返しの数量詞として、 (つまり、ゼロ以上の)繰り返しがが進まない/欲張りでない.*?ことを示します。*
  • (…)グループ化に使用されます。(?:…)非キャプチャグループです。キャプチャグループは、一致する文字列を保存します。バック/フォワード/ネストされた参照(例\1)、置換置換(例$2)などが可能です。
  • (?=…)ポジティブな先読みです; 与えられたパターンの一致があると断言するのは右に見えます。(?<=…)ポジティブな後ろ姿です; 左に見えます。

言語参照/追加リソース

4

1 に答える 1

9

概要

高レベルでは、パターンは任意の1つの文字に一致しますが、グループ2に一致した文字の反転「メイト」をキャプチャ.するアクションを追加で実行します。このキャプチャは、長さが一致する入力文字列のサフィックスを作成することによって行われます。grab$2現在の位置までのプレフィックスの長さ。これを行うassertSuffixには、接尾辞を1文字増やすパターンに適用し、これを1回繰り返しforEachDotBehindます。グループ1はこのサフィックスをキャプチャします。グループ2でキャプチャされたその接尾辞の最初の文字は、一致した文字の反転「メイト」です。

したがって、一致した各文字をその「メイト」に置き換えると、文字列を逆にする効果があります。


仕組み:より簡単な例

正規表現パターンがどのように機能するかをよりよく理解するために、最初にそれをより単純な入力に適用してみましょう。また、置換パターンでは、キャプチャされたすべての文字列を「ダンプ」するだけなので、何が起こっているのかをよりよく理解できます。これがJavaバージョンです。

System.out.println(
    "123456789"
        .replaceAll(REVERSE, "[$0; $1; $2]\n")
);

上記の印刷物(ideone.comで見られるように):

[1; 9; 9]
[2; 89; 8]
[3; 789; 7]
[4; 6789; 6]
[5; 56789; 5]
[6; 456789; 4]
[7; 3456789; 3]
[8; 23456789; 2]
[9; 123456789; 1]

したがって、たとえば[3; 789; 7]、ドットが一致した3(グループ0でキャプチャされた)ことを意味し、対応するサフィックスは789(グループ1)であり、その最初の文字は7(グループ2)です。7それが3の「仲間」であることに注意してください。

                   current position after
                      the dot matched 3
                              ↓        ________
                      1  2 [3] 4  5  6 (7) 8  9
                      \______/         \______/
                       3 dots        corresponding
                       behind      suffix of length 3

キャラクターの「仲間」は、その右または左にある可能性があることに注意してください。キャラクターはそれ自身の「仲間」でさえあるかもしれません。


接尾辞の作成方法:ネストされた参照

増大する接尾辞のマッチングと構築を担当するパターンは次のとおりです。

    ((.) \1?)
    |\_/    |
    | 2     |       "suffix := (.) + suffix
    |_______|                    or just (.) if there's no suffix"
        1

グループ1の定義内には、それ自体への参照(with \1)がありますが、オプション(with ?)であることに注意してください。オプションの部分は、グループがそれ自体を参照せずに一致する方法である「基本ケース」を提供します。グループがまだ何もキャプチャしていない場合、グループ参照を照合する試みは常に失敗するため、これが必要です。

グループ1が何かをキャプチャすると、前回キャプチャしたサフィックスが今回も残っているため、セットアップでオプションの部分が実行されることはありません。このサフィックスの先頭に。を付けて、いつでも別の文字を追加できます(.)。この先頭に追加された文字は、グループ2にキャプチャされます。

したがって、このパターンは接尾辞を1ドット増やしようとします。したがって、これを1回繰り返すforEachDotBehindと、現在の位置までのプレフィックスの長さとまったく同じ長さのサフィックスが作成されます。


方法assertSuffixforEachDotBehind動作:メタパターンの抽象化

assertSuffixこれまで、ブラックボックスとして扱ってきたことに注意してくださいforEachDotBehind。実際、この議論を最後に残すことは意図的な行為です。名前と簡単なドキュメントは、それらが何をするかを示唆しおり、これは私たちがREVERSEパターンを書いたり読んだりするのに十分な情報でした!

よく調べてみると、これらの抽象化のJavaとC#の実装はわずかに異なっていることがわかります。これは、2つの正規表現エンジンの違いによるものです。

.NET正規表現エンジンでは、後読みで完全な正規表現が可能であるため、これらのメタパターンはそのフレーバーではるかに自然に見えます。

  • AssertSuffix(pattern) := (?=.*$(?<=pattern))つまり、先読みを使用して文字列の最後まで移動し、ネストされた後読みを使用してパターンを接尾辞と照合します。
  • ForEachDotBehind(assertion) := (?<=(?:.assertion)*).*つまり、キャプチャされていないグループ内のドットとともにアサーションにタグを付けて、後読みで一致させるだけです。

Javaは公式には無限長のルックビハインドをサポートしていないため(ただし、特定の状況ではとにかく機能します)、対応するものはもう少し厄介です。

  • assertSuffix(pattern) := (?<=(?=^.*?pattern$).*)つまり、ルックビハインドを使用して文字列の先頭まで移動し、ネストされた先読みを使用して文字列全体を照合し、接尾辞パターンの.*?前に無関係なプレフィックスを不本意に照合します。
  • forEachDotBehind(assertion) := (?<=^(?:.assertion)*?)つまり、不本意な繰り返しで固定されたルックビハインドを使用します。つまり、^.*?(同様に、非キャプチャグループ内のドットとともにアサーションにタグを付けます)。

これらのメタパターンのC#実装はJavaでは機能しませんが、Java実装はC#で機能することに注意してください(ideone.comを参照)。したがって、C#とJavaで実際に異なる実装を行う必要はありませんが、C#の実装では、より強力な.NET正規表現エンジンのルックビハインドサポートを意図的に利用して、パターンをより自然に表現しました。

したがって、メタパターンの抽象化を使用する利点を示しました。

  • これらのメタパターンの実装を独自に開発、調査、テスト、最適化などすることができます。おそらく、フレーバー固有の機能を利用して、パフォーマンスや読みやすさを向上させることができます。
  • これらのビルディングブロックが開発され、十分にテストされたら、それらをより大きなパターンの一部として使用できます。これにより、より読みやすく、保守しやすく、よりポータブルなソリューションのために、より高いレベルでアイデアを表現できます。
  • メタパターンは再利用を促進し、プログラムによる生成は重複が少ないことを意味します

概念のこの特定の表現はかなり原始的ですが、これをさらに進めて、十分にテストされ最適化されたメタパターンのライブラリを使用して、より堅牢なプログラムによるパターン生成フレームワークを開発することもできます。

も参照してください


まとめ

正規表現を使用して文字列を逆にすることは、実際には良い考えではないことを繰り返す必要があります。必要以上に複雑で、パフォーマンスはかなり悪いです。

とは言うものの、この記事は、それが実際に実行できること、およびメタパターン抽象化を使用してより高いレベルで表現された場合、ソリューションが実際に非常に読みやすいことを示しています。ソリューションの重要なコンポーネントとして、ネストされた参照が、うまくいけば別の魅力的な例でもう一度紹介されます。

あまり具体的ではありませんが、おそらくこの記事には、最初は難しい(または「不可能」にさえ見える)問題を解決するために必要な決意も示されています。おそらくそれはまた、主題のより深い理解、多くの研究と努力の結果に伴う思考の明晰さを示しています。

間違いなく正規表現は恐ろしい問題になる可能性があり、確かにそれはあなたのすべての問題を解決するように設計されているわけではありません。しかし、これは憎むべき無知の言い訳にはなりません。そして、あなたが学ぶ気があるなら、これは驚くほど深い知識の1つです。

于 2010-09-12T04:07:05.543 に答える