(C++)正規表現で、1行の文字列内でパターンマッチする全ての文字列を出力したい

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
Ketty

(C++)正規表現で、1行の文字列内でパターンマッチする全ての文字列を出力したい

#1

投稿記事 by Ketty » 8年前

初めまして、Kettyと申します。
C++(stl)の正規表現(std::regex_search)について、質問させてください。

今、私がやりたいと思っていることは、
正規表現をつかって1行の文字列を検索し、特定のパターンにマッチする複数の、全ての文字列を出力したい、
というものですが、マッチングは成功するものの、その後の出力が期待通りでなく、悩んでいます。

まず、環境は以下の通りです。
----------------------------------------------------------------------------------------
OS:Windows7 64bit
開発環境:Visual studio C++ 2010 Express Edition
使用ライブラリ:DXライブラリ(バージョン 3.11)

DXライブラリを使用している理由は、
現在ゲームを開発中で、そのゲームの処理内で上記マッチングをしたいからです。
----------------------------------------------------------------------------------------

前置きが長くなりましたが、本題です。
検索する全体文字列は、以下の1Byte文字だけで構成された(ちょっと変な)英文です。

IF(You love DXLib) AND(CPlusPlus) THEN(Create Funny Games!)

上記の英文から、"IF(○○)"と、"AND(○○)"と、"THEN(○○)" を取得して、表示したく、
マッチングパターンとして以下を考えましたが、"IF(○○)"しか表示できず、解決策に困っています。

以下、ソースコードとなります(実行に必要なミニマムなコードです)。
(1)まず、どこがまずいのでしょうか?
私はマッチングパターンが悪いのだと考えていますが、なぜ悪いのかがわかりません。
(2)次に、では、どのようにすれば、"IF(○○)"と、"AND(○○)"と、"THEN(○○)" を全て表示できるのでしょうか?
これはひょっとして、答えを教えてくださいと言ってるのと同じかもしれませんので、
本掲示板の規約に対して差支えるようでしたら、なにかヒントのような形でも教えていただければ幸いです。

※現在は、他のライブラリ(boostなど)の導入は検討しておらず、stlだけでなんとかできれば・・・と考えてます。

どうか皆様、お知恵をお貸しください。よろしくお願いします。

コード:

#include "DxLib.h"
#include <regex>
#include <string>

using namespace std ;

// プログラムは WinMain から始まります
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
						LPSTR lpCmdLine, int nCmdShow )
{
	// ウィンドウモードにする
	ChangeWindowMode( TRUE ) ;

	// DXライブラリ初期化処理
	if( DxLib_Init() == -1 ){ return -1 ; }

	//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	// 文字列の中から、正規表現を使って、
	// "IF(○○)"と、"AND(○○)"と、"THEN(○○)"を表示させたい
	//
	// 【文字列】
	//	IF(You love DXLib) AND(CPlusPlus) THEN(Create Funny Games!)
	//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	// 検索対象の文字列
	const string target = "IF(You love DXLib) AND(CPlusPlus) THEN(Create Funny Games!)" ;
	// 正規表現の検索パターン「IF(○○)やAND(○○)やTHEN(○○)」までを1セットとして扱う
	string searchStr = "[A-Z]+\\(.{1,20}\\)" ;

	// 正規表現の検索パターン用オブジェクトを生成
	std::regex searchPattern( searchStr ) ;
	// 正規表現マッチング結果を格納するオブジェクトを宣言
	std::match_results<const char *> matchResult ;

	// マッチングする
	bool isMatched = std::regex_search( target.c_str(), matchResult, searchPattern ) ;

	// 以下、マッチした場合の処理
	if( isMatched )
	{
		// 表示する文字色(白)で初期化
		int fontColor = GetColor( 255, 255, 255 ) ;
		// 表示位置(Y座標)を初期化
		int drawPosY = 0 ;

		//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
		// 「IF(You love DXLib)」しか表示されない・・・orz
		// 「AND(CPlusPlus)」やTHEN(Create Funny Games!)はどうやれば拾えるのか??
		//  正規表現の検索パターンが悪いのだと思うけれど、どう直せばよいのか??
		//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
		// 正規表現マッチング結果をひとつずつ取り出す
		std::match_results<const char *>::const_iterator it = matchResult.begin() ;
		while( it != matchResult.end() )
		{
			if( it->matched )
			{
				// マッチした文字列を取得する
				string matchedStr = it->str() ;

				// 表示位置を毎回30ピクセルずつ下に下げる
				drawPosY += 30 ;
			
				// 画面に表示する
				DrawString( 240 , drawPosY , matchedStr.c_str() , fontColor ); 
			}

			++it ;
		}
	}

	// キー入力待ち
	WaitKey() ;

	// DXライブラリ使用の終了処理
	DxLib_End() ;
	// ソフトの終了
	return 0 ;				
}
初めての投稿なので、ソソウのないようにと慎重に書いたせいで、
ものすごく堅苦しい言葉づかいで、理解しづらい文面になってしまったかもしれません・・・お許しください(^^;

アバター
みけCAT
記事: 6626
登録日時: 11年前
住所: 千葉県
連絡を取る:

Re: (C++)正規表現で、1行の文字列内でパターンマッチする全ての文字列を出力したい

#2

投稿記事 by みけCAT » 8年前

この検証用コードにDXライブラリを使用する必然性があるのでしょうか?
DXライブラリが使われていると、「とりあえずIdeoneで動かしてみる」などの手法が使えなくなります。
(コードがwebへ流出するのを防ぐ目的があるのなら構いませんが)
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

アバター
みけCAT
記事: 6626
登録日時: 11年前
住所: 千葉県
連絡を取る:

Re: (C++)正規表現で、1行の文字列内でパターンマッチする全ての文字列を出力したい

#3

投稿記事 by みけCAT » 8年前

とりあえずPerlで試しましたが、正規表現のパターンは問題なさそうです。
http://ideone.com/lsq8fH
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

アバター
h2so5
副管理人
記事: 2212
登録日時: 11年前
住所: 東京
連絡を取る:

Re: (C++)正規表現で、1行の文字列内でパターンマッチする全ての文字列を出力したい

#4

投稿記事 by h2so5 » 8年前

std::regex_searchは最初にマッチしたパターンのみを返します。
複数回マッチさせるためには検索開始位置をずらしながら複数回std::regex_searchを呼んでください。

Ketty

Re: (C++)正規表現で、1行の文字列内でパターンマッチする全ての文字列を出力したい

#5

投稿記事 by Ketty » 8年前

>みけCAT さん
お返事くださりありがとうございます。

>DXライブラリが使われていると、「とりあえずIdeoneで動かしてみる」などの手法が使えなくなります。
Ideoneでregex_searchが誤作動を起こす・・・、あるはサポートされていない、などといった記事を、
なにかの掲示板で見た記憶があり、特に検証もせず、Ideoneの使用を控えていました。
出典元を忘れましたが。記憶違いかな・・・。

Perlのサンプル、ありがとうございます。
参考とさせていただきます。
ただ、Perlの知識はほぼ無く、ご提示いただいた内容の理解がまだなのですが、
<matched>というキーワードを見る限り、これはPerl仕様な正規表現ですね?

>h2so5 さん
お返事くださり、ありがとうございます。

>std::regex_searchは最初にマッチしたパターンのみを返します。
なんと・・・そうだったのですか・・・。
ありがとうございました、教えていただかなかったら迷宮入りするところでした。
検索位置をずらしながら~を、実装してみます。

実装できましたら、こちらに提示します。

アバター
みけCAT
記事: 6626
登録日時: 11年前
住所: 千葉県
連絡を取る:

Re: (C++)正規表現で、1行の文字列内でパターンマッチする全ての文字列を出力したい

#6

投稿記事 by みけCAT » 8年前

Ketty さんが書きました:ただ、Perlの知識はほぼ無く、ご提示いただいた内容の理解がまだなのですが、
<matched>というキーワードを見る限り、これはPerl仕様な正規表現ですね?
確かにPerl仕様の書き方ですが、<matched>は適当に書いただけなので関係ないです。
http://ideone.com/x09Vmy
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

アバター
h2so5
副管理人
記事: 2212
登録日時: 11年前
住所: 東京
連絡を取る:

Re: (C++)正規表現で、1行の文字列内でパターンマッチする全ての文字列を出力したい

#7

投稿記事 by h2so5 » 8年前

match_resultsだと複数回マッチに対応しているように見えますが、実際にはサブマッチの內容が格納されます。

たとえばこのような対象文字列と正規表現でregex_searchを実行した場合
文字列: ABC123DEF456GHI789
正規表現: ([A-Z]+)([0-9]+)

正規表現全体にマッチするのは3つです。
ABC123 DEF456 GHI789

しかしmatch_resultsが格納しているのは最初のマッチ結果の中の、カッコで括られた部分それぞれのマッチ結果です。
ABC 123

ちなみに一番最初には全体のマッチ結果が入りますので、match_resultsの中身はこうなります。
ABC123 ABC 123

IF(You love DXLib) のみが表示されたのはこれが原因です。

Ketty

Re: (C++)正規表現で、1行の文字列内でパターンマッチする全ての文字列を出力したい

#8

投稿記事 by Ketty » 8年前

>みけCAT さん
ありがとうございます。

>確かにPerl仕様の書き方ですが、<matched>は適当に書いただけなので関係ないです。
ああ、なるほど。やっと理解できました。
てっきりPerlの予約語か何かだと勘違いしました(^^; すみません。

しかし、Perlだと(C++に比べて)ずいぶん簡単そうに書けるんですね。

>h2so5 さん

詳しい解説を書いてくださり、ありがとうございます。わかりやすかったです。

>しかしmatch_resultsが格納しているのは最初のマッチ結果の中の、カッコで括られた部分それぞれのマッチ結果です。
最初、このくだりを読ませていただいて、なぜカッコでくくられた部分のそれぞれが・・・?
と、まったく理解できなかったのですが、調べてみてわかりました。
カッコでくくるとグループ化され、それらは内部では、のちの参照用としてキャッシュされるというやつですね。

ともあれ、「検索位置をずらして~」が、実装できましたので、ここに晒します。

コード:

#include "DxLib.h"
#include <regex>
#include <string>

using namespace std ;

// プログラムは WinMain から始まります
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
						LPSTR lpCmdLine, int nCmdShow )
{
	// ウィンドウモードにする
	ChangeWindowMode( TRUE ) ;

	// DXライブラリ初期化処理
	if( DxLib_Init() == -1 ){ return -1 ; }

	//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	//	文字列の中から、正規表現を使って、
	//	"IF(○○)"と、"AND(○○)"と、"THEN(○○)"を表示させます
	//
	// 【文字列】
	//	IF(You love DXLib) AND(CPlusPlus) THEN(Create Funny Games!)
	//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	// 検索対象の文字列
	const string target = "IF(You love DXLib) AND(CPlusPlus) THEN(Create Funny Games!)" ;
	// 正規表現の検索パターン「IF(○○)やAND(○○)やTHEN(○○)」までを1セットとして扱う
	string searchStr = "[A-Z]+\\(.{1,20}\\)" ;

	// 正規表現の検索パターン用オブジェクトを生成
	std::regex searchPattern( searchStr ) ;
	// 正規表現マッチング結果を格納するオブジェクトを宣言
	std::match_results<const char *> matchResult ;

	// マッチした文字列を格納するためのバッファを用意
	vector<string> vecMatchStr(0) ;
	// マッチしたかどうかを示す変数を初期化
	bool isMatched = true ;
	// 検索対象の文字列を別の変数にコピーする(以下のループではこの変数を使ってマッチングを行う)
	string tmpTarget = target ; ;
	//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	// マッチング処理
	//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	// ループでマッチング→検索位置をずらす→マッチングを繰り返す(マッチするものがなくなったら終了)
	while( isMatched )
	{
		// マッチングする
		isMatched = std::regex_search( tmpTarget.c_str(), matchResult, searchPattern ) ;

		// 以下、マッチした場合の処理
		if( isMatched )
		{
			// 正規表現マッチング結果を取り出す
			std::match_results<const char *>::const_iterator it = matchResult.begin() ;
			while( it != matchResult.end() )
			{
				if( it->matched )
				{
					// マッチした文字列を取得
					string matchStr = it->str() ;
					// バッファに追記
					vecMatchStr.push_back( matchStr ) ;

					// コピーした文字列から今回のマッチ文字列を除去
					tmpTarget = tmpTarget.substr( matchStr.size(), tmpTarget.size() - matchStr.size() ) ;
				}

				++it ;
			}
		}
	}

	//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	// マッチングした文字列をウィンドウに表示する処理
	// (マッチングした文字列がなければ何も表示されない)
	//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	// 表示する文字色(白)で初期化
	int fontColor = GetColor( 255, 255, 255 ) ;
	// 表示位置(Y座標)を初期化
	int drawPosY = 0 ;
	for( int i=0, n=vecMatchStr.size() ; i<n ; ++i )
	{
		// 表示位置を毎回30ピクセルずつ下に下げる
		drawPosY += 30 ;
		// 画面に表示する(あとでDrawStringToHandleに書き換えるべき)
		DrawString( 240 , drawPosY , vecMatchStr[i].c_str() , fontColor ); 
	}

	// キー入力待ち
	WaitKey() ;

	// DXライブラリ使用の終了処理
	DxLib_End() ;
	// ソフトの終了
	return 0 ;				
}

Ketty

Re: (C++)正規表現で、1行の文字列内でパターンマッチする全ての文字列を出力したい

#9

投稿記事 by Ketty » 8年前

解決させていただきます。皆様どうもありがとうございました。

アバター
h2so5
副管理人
記事: 2212
登録日時: 11年前
住所: 東京
連絡を取る:

Re: (C++)正規表現で、1行の文字列内でパターンマッチする全ての文字列を出力したい

#10

投稿記事 by h2so5 » 8年前

Kettyさんのコードでは、
"______________________________IF(You love DXLib) AND(CPlusPlus) THEN(Create Funny Games!)"
のような文字列を入力した時にsubstrでマッチ文字列が消えないので誤作動します。

より簡潔に書くとこうなります。
イテレータを利用しているので無駄なコピーが発生しません。

(11/16 9:40 ちょっと修正)

コード:

#include <regex>
#include <string>
#include <iostream>

using namespace std;

int main() {
  const string target = "IF(You love DXLib)AND(CPlusPlus)THEN(Create Funny Games!)" ;
  string searchStr = "[A-Z]+\\(.{1,20}\\)" ;

  regex searchPattern( searchStr ) ;
  smatch matchResult;

  for (auto it = target.begin();
	  regex_search(it, target.end(), matchResult, searchPattern);
	  it += matchResult.position(0) + matchResult.length(0)) {

    cout << matchResult.str(0) << endl;
  }
  return 0;
}
実行結果

コード:

IF(You love DXLib)
AND(CPlusPlus)
THEN(Create Funny Games!)

Ketty

Re: (C++)正規表現で、1行の文字列内でパターンマッチする全ての文字列を出力したい

#11

投稿記事 by Ketty » 8年前

>h2so5 さん

おお!こ、これは・・・私の(バグつき)ソースと比べて、とてもスリムですね!!!
実行してみたところ、動作も希望のものでした。
こういうのを、ふつくしいソースというんでしょうね(^^)

>"______________________________IF(You love DXLib) AND(CPlusPlus) THEN(Create Funny Games!)"
>のような文字列を入力した時にsubstrでマッチ文字列が消えないので誤作動します。
なるほど。おっしゃるとおり、私のソースは永久ループに陥る致命的な問題を持っておりました。
ご指摘くださり、ありがとうございましたm(__)m

>smatch matchResult;
正直、ご提示いただいたソースを、まだ完全には理解できておりません。
デバッグ情報をおいかけながら、以下のことが分かりました。
.str(0)でマッチ文字列をとれる、
.position(0)で、イテレータを進めるために必要な現在ポジションが取得できる、
.length(0)は、.str(0).length()と等価のようだ・・・

しかし、この3つ全てに出現している(0)というのが、何なのか、なぜ0指定で取得できるのか、
分かっておりません。

遅ればせながら、ここまでご丁寧にご対応くださったことに改めて感謝いたします。

アバター
h2so5
副管理人
記事: 2212
登録日時: 11年前
住所: 東京
連絡を取る:

Re: (C++)正規表現で、1行の文字列内でパターンマッチする全ての文字列を出力したい

#12

投稿記事 by h2so5 » 8年前

0番目のマッチ結果には全体のマッチ結果が格納されるという仕様になっています。
これはどの言語やライブラリでもだいたい似たようなものだと思います。

くわしいことはここに書いてあります
http://en.cppreference.com/w/cpp/regex/match_results

Ketty

Re: (C++)正規表現で、1行の文字列内でパターンマッチする全ての文字列を出力したい

#13

投稿記事 by Ketty » 8年前

>h2so5 さん
お返事くださりありがとうございます。

>これはどの言語やライブラリでもだいたい似たようなものだと思います。
なるほど、マッチした文字列自体を参照するという処理の実装は今回が初めてだったのですが、
だいたいの言語で似たようなものであるなら、今後、別の言語で正規表現を扱う場合にも、ひとつの知識として活用できそうです。

>くわしいことはここに書いてあります
>http://en.cppreference.com/w/cpp/regex/match_results
やはりそのページなのですね。
そのページは今回の問題で真っ先に見たことがありますが、
いかんせん、私は英文を訳読するのが得意でないのでスルーしていました・・・(==;

このたびは、丁寧な対応をしてくださり、ありがとうございました。
あらためて、お礼申し上げます。

閉鎖

“C言語何でも質問掲示板” へ戻る