正規表現と和解せよ【ABC049C - 白昼夢】

ABC049C-白昼夢
atcoder.jp

やること

  • 文字列Sを受け取る
  • Sが([dream]or[dreamer]or[erase]or[eraser])の繰り返しと等しいか判定する
  • 正規表現から逃げるな

解答 正規表現
文字列を扱う上で正規表現から逃げていたら何も解決しません。
今まで逃げてきましたが、今日こそ正規表現に対する苦手意識を克服します。
cpprefjp.github.io
C++日本語リファレンスによると、何やらusing宣言されていますので
正規表現で判定したい文字列がchar型のときはregex nautokaと宣言すればbasic_regexとして機能し、
wchar型のときはwregex nantokaと宣言すればbasic_regexとして機能するようです。
char型とwchar型のことはとりあえずさておき、
まあ今回は普通にchar型として問題なく動くよねってことで今回はregex正規表現を読み込んでもらいます。

その正規表現をどこからどうやって理解しようか……と足踏みしていたのですが
アルゴ式のところでコードテスト付きで正規表現が学べるコース(無料)がありました
algo-method.com
練習問題をいくつかやった結果、ちょっと感覚がつかめたのでコードが書けました。(ダイレクトマーケティング

#include <bits/stdc++.h>
using namespace std;
int main(){
  string S;
  cin>>S;
  regex reg(R"(     (dream|dreamer|erase|eraser)*     )");//正規表現
  bool YES = regex_match(S,reg);//完全一致検索,返り値はbool
  if(YES){
    cout << "YES" <<endl;
  }else{
    cout << "NO" <<endl;
  }
}

regex reg(R"( )");のR"()"は生文字列リテラルといって、エスケープ文字を使わなくてもまあまあ許してくれるやつです。
正直今回は不要ですが覚えておいて損はないはず。
あの恐ろしき正規表現部分が(dream|dreamer|erase|eraser)*ですね。
|はorと同じ意味なので、dream dreamer erase eraserのいずれかに完全一致するかどうか、という意味なります。

*は手前の文字の0回以上の繰り返しがあるかどうかを判定します。
手前を()でくくってひとかたまりにしているので(x)*、(x)の繰り返しを判定します。
(w|x|y|z|)*はw x y zのいずれかが繰り返されているか、という意味になります。
(dream|dreamer|erase|eraser)*は正規表現語で問題文をそのまま言い換えただけです。

regex_match(string,regex);は完全一致検索(string全体が正規表現と一致するか)
regex_search(string,regex);は部分一致検索をします。(stringの任意の箇所が正規表現と一致するか)
今回は完全一致してもらわないと(ゴミが残る)困るので、regex_matchで調べます。
成否を判定するので、返り値はbool型のtrueかfalseになります。そのままif文の判定式に突っ込んでも構いません。
よしこれで正規表現とはお別れや!

解答 真の正規表現
世の中は繰り返しが多いコンテンツに厳しいです。
コードの中に繰り返しが多いコンテンツが見つかるとき、大体短縮する方法があります。困ります。
dreamdreamerって5文字一致してるし、eraseeraserも5文字一致してるし、なんだか無駄がありますね? ケチかよ。

  regex reg(R"((dream|dreamer|erase|eraser)*)");//正規表現
  regex regreg(R"((dream(er){0,1}|erase(r){0,1})*)");//真の正規表現
  regex sinreg(R"((dream(er)?|eraser?)*)");//シン・正規表現

{min,max}は直前の文字がmin回以上max回以下の繰り返しがあるかについて判定します。
(r)はくくってありますが、くくらなくても同じ意味です。
{0,1}と?は同じ意味を持ちます。より文字数を減らしてスマートになった(dream(er)?|eraser?)*がシン・正規表現です。
上記3つはどれも結果が同じです。
好きなやつを使いましょう。正規表現のこと好きになれねえや。