第 1 章 Visual C# による文字列処理入門
〜 アルゴリズム入門 〜
サンプルプログラムのダウンロード (msdnaa_algorithm01.exe、118 KB)
1.1 はじめに
近年、複雑化・高機能化の一途をたどるソフトウェア開発の現場では、生産性を向上させるために様々な開発手法が考案されてきました。その中でも、オブジェクト指向プログラミングは、プログラムを互いに独立した部品に分割するという特徴があります。そのため、現在最も普及しています。オブジェクト指向プログラミングの登場により、複雑なプログラムの生産性が大きく向上しており、現在のソフトウェア開発において無くてはならないほどの存在となってきています。
一方、ビジネスの現場等では、日本でも本格的なインターネットの普及が始まり、ネットワークを利用した Web ベースのアプリケーションが身近なものとなってきました。しかし、従来、ネットワークを利用したプログラミングには、多くのノウハウが必要でした。また、Web サービス同士の相互利用が困難であるという問題点もありました。
そこで、現在、オブジェクト指向と Web という 2 つの特徴を持った Visual C# が、注目されています。Visual C# は、Microsoft 社の .NET 戦略上で重要な位置を占める言語であり、Web アプリケーション開発を始めとする様々なアプリケーション開発の現場でその高い開発効率とハイパフォーマンスを実現してくれます。特に、Visual C# から利用できる .NET Framework のコントロールやクラスは、部品を組み立てる感覚で利用できることができるため、プログラムの開発効率を大きく向上させ、堅牢なプログラムを開発できることが大きな特徴です。
しかし、プログラムの設計・開発において、いつの時代にも普遍的に重要なものがあります。それが、アルゴリズムです。アルゴリズムは、計算量等に大きな影響を及ぼすため、プログラムには必要不可欠です。そこで、本連載は、6 回に分けて、テキスト処理、グラフィック処理、画像処理、知識情報処理などのアルゴリズムを Visual C# を用いて解説します。そして、本章では、文字列の構造や、正規表現による検索、置換などの文字列処理について解説します。
1.2 String クラスとは
本章で解説する文字列処理は、構文解析や検索、入力値のチェック等様々な用途で利用されています。そのため、文字列処理は、プログラムの基本と言っても過言ではありません。Visual C# では、文字列処理を行うために String クラスが用意されています。
String クラスは、System.String 名前空間に存在し、文字列を利用する時に用います。文字列は、String クラスのインスタンスとして扱われます。String クラスのインスタンスは、作成時点以降に値を変更できないことから、不変型と呼ばれます。String インスタンスを変更するように見えるメソッドは、実際には変更内容が反映された新しい String インスタンスを返します。文字列の内容を変更する必要がある場合には、第 1.3 節の StringBuilder クラスを使用します。String インスタンスの値を "Microsoft" から "MSDNAA" に変更した場合のイメージを図 1 に示します。

図 1 : String インスタンスのイメージ図
1.2.1 string 型とは
String インスタンスの生成には、一般的に string 型を用います。string 型は、System.String のエイリアスとして定義されています。エイリアスとは、より速く入力などができるように、完全な名前空間に短い名前を付けることです。string 型は、Unicode 文字列です。
1.2.2 インデックスとは
インデックスとは、文字列内の文字の位置です。インデックスは、文字列の 1 番目の文字から始まります。1 番目の文字のインデックス位置は、0 です。つまり、文字列のインデックスは、0 から始まります。

図 2 : インデックスのイメージ図
1.2.3 String クラスのメンバ例
String クラスには、文字列を処理するための様々なプロパティやメソッドが用意されています。本項では、String クラスの一般的なプロパティやメソッドについて解説します。
(1) 文字列の長さ (Length プロパティ)
Length プロパティは、文字列の文字数を取得します。String クラスのインスタンスは、不変型であるため、文字列の長さを取得できますが設定はできません。
string sText = "Microsoft"; //文字列の宣言
int iCount; //文字数を入れる変数の宣言
iCount = sText.Length; //文字数の取得メソッド
sText の文字列は、"Microsoft" の 9 文字から構成されています。そのため、iCount の値は、9 になります。
(2) 文字列の挿入 (Insert メソッド)
Insert メソッドは、指定したインデックス位置に文字列を挿入します。
|
public string Insert (int 挿入先インデックス , string 挿入文字列 );
|
string sText1 = "Microft" , sText2 = "so"; //文字列の宣言
sText1 = sText1.Insert(5 , sText2); //文字列の挿入メソッド
sText1 の値は、sText1 のインデックス 5 の位置に sText2 の文字列を挿入するため、"Microsoft" になります。String クラスのインスタンスは、不変型であるため、文字列 "Microsoft" を入れる新しい sText1 インスタンスが生成されています。
(3) 文字列の削除 (Remove メソッド)
Remove メソッドは、文字列の指定位置から指定した数の文字を削除します。
|
public string Remove (int 削除開始インデックス , int 削除文字数);
|
string sText = "Microsoft"; //文字列の宣言
sText = sText.Remove(5 , 4); //文字列の削除メソッド
sText の値は、文字列 "Microsoft" のインデックス 5 から 4 文字削除するため、"Micro" になります。String クラスのインスタンスは、不変型であるため、文字列 "Micro" を入れる新しい sText インスタンスが生成されています。
(4) 文字列の置換 (Replace メソッド)
Replace メソッドは、文字列のある部分を指定した文字列にすべて置換します。
|
public string Replace (string 置換される文字列 , string 置換する文字列);
|
string sText = "Microsoftsoft"; //文字列の宣言
string sOldText = "so" , sNewText = "aa"; //文字列の宣言
sText = sText.Replace(sOldText, sNewText); //文字列の置換メソッド
sText の値は、文字列 "Microsoftsoft" の文字列 "so" がすべて "aa" に置換されるため、" Microaaftaaft" になります。String クラスのインスタンスは、不変型であるため、部分的な置換はできません。そのため、文字列 "so" がすべて "aa" に置換されます。部分的な置換を行う場合は、第 1.3 節で解説する StringBuilder クラスの Replace メソッドを用います。
(5) 文字列の切り取り (Substring メソッド)
Substring メソッドは、ある文字列から部分文字列を取得します。この部分文字列は、指定した文字位置から開始し、指定した文字数の文字列です。
|
public string Substring (int部分文字列開始インデックス , int 部分文字列の文字数);
|
string sText = "Microsoft"; //文字列の宣言
sText = sText.Substring(5 , 4); //文字列の切り取りメソッド
sText の値は、文字列 "Microsoftsoft" のインデックス 5 から 4 文字の部分文字列を取得するため、"soft" になります。String クラスのインスタンスは、不変型であるため、文字列 "soft" を入れる新しい sText インスタンスが生成されています。
1.3 StringBuilder クラスとは
Visual C# では、可変型の文字列処理を行うために StringBuilder クラスが用意されています。StringBuilder クラスは、System.Text.StringBuilder 名前空間に存在し、文字列を利用する時に用います。StringBuilder クラスのインスタンスは、String クラスのインスタンスと異なり、文字を追加、削除、置換または挿入して値を作成した後にその値を変更できるため、可変型と呼ばれます。StringBuilder クラスは、可変型であるため、String クラスに比べ文字列を変更するプロパティやメソッドが多く用意されています。そのため、StringBuilder クラスは、連続した操作を次々にチェイン化する場合等に役立ちます。StringBuilder インスタンスの値を "Microsoft" から "MSDNAA" に変更した場合のイメージを図 3 に示します。

図 3 : StringBuilderインスタンスのイメージ図
1.3.1 StringBuilder クラスのメンバ例
StringBuilder クラスには、文字列を処理するための様々なプロパティやメソッドが用意されています。StringBuilder クラスは、可変型であるため、String クラスに比べ文字列を変更するプロパティやメソッドが多く用意されています。本項では、StringBuilder クラスの一般的なプロパティやメソッドについて解説します。
(1) 文字列の長さ (Length プロパティ)
Length プロパティは、文字列の文字数を取得・設定します。StringBuilder クラスのインスタンスは、可変型であるため、文字列の長さの取得と設定ができます。
StringBuilder sbText = new StringBuilder("Microsoft"); //文字列の宣言
int iCount1 , iCount2=15; //文字数の宣言
iCount1 = sbText.Length; //文字数の取得メソッド
sbText.Length = iCount2; //文字数の設定メソッド
sbText の文字列は、"Microsoft" の 9 文字から構成されています。そのため、iCount1 の値は、文字数を取得しているため 9 になります。文字数の設定では、文字列 sbText の文字数を 15 に設定しています。現在の文字数より少ない値を設定した場合は、指定した文字数までに切り捨てられます。
(2) 文字列の置換 (Replace メソッド)
Replace メソッドは、文字列のある部分を指定した文字列に置換します。このメソッドは、String クラスの Replace メソッドとは異なり、部分的な置換を行うことができます。
|
public string Replace (string 置換される文字列 , string 置換する文字列
int 部分文字列の開始インデックス , int 部分文字列の文字数);
|
StringBuilder sbText = new StringBuilder("Microsoftsoft"); //文字列の宣言
string sOldText = "so" , sNewText = "aa"; //文字列の宣言
sbText = sbText.Replace(sOldText, sNewText, 5 , 2); //文字列の置換メソッド
sbText の値は、文字列 "Microsoftsoft" のインデックス 5 から 2 文字後までにある文字列 "so" のすべてを "aa" に置換するため、" Microaaftsoft" になります。
1.4 正規表現とは
正規表現とは、文字列のパターンの検索や置換のための正確で柔軟性の高い表記方法です。正規表現は、文字列の検索等を容易に行うことができます。本節では、正規表現を用いた文字列の検索方法を解説します。
1.4.1 正規表現の構文
検索文字列の文字や数字の指定には、正規表現の構文が使用できます。正規表現の構文例を表 1 に示します。
表 1 : 正規表現の構文例
| 表現 | 構文 | 説明 |
| 任意の文字 | . | 改行を除く任意の 1 文字を検索します。 |
| 最長 - 0 回以上の繰り返し | * | 直前の正規表現の 0 回以上の繰り返しを検索します。 |
| 最長 - 1 回以上の繰り返し | + | 直前の正規表現の 1 回以上の繰り返しを検索します。 |
| 最短 - 0 回以上の繰り返し | @ | 直前の正規表現の 0 回以上の繰り返しを検索します。一致する文字列の長さを最小限にします。 |
| 最短 - 1 回以上の繰り返し | # | 直前の正規表現の 1 回以上の繰り返しを検索します。一致する文字列の長さを最小限にします。 |
| セット内の文字 | [] | [] 内の文字のいずれかを検索します。文字の範囲を指定するには,[a-z] のように開始と終了の文字をハイフン (-) でつなぎます。 |
| セット外の文字 | [^...] | カレット (^) の後の文字の集合に含まれない文字を検索します。 |
1.4.2 正規表現クラス
Visual C# で正規表現を利用する場合は、正規表現クラスを用います。本項では、.NET Framework の正規表現クラスについて解説します。
(1) Regex クラス
Regex クラスは、System.Text.RegularExpressions.Regex 名前空間に存在し、正規表現を利用する時に用います。Regex クラスは、変更不可 (読み込み専用) の正規表現を表します。Regex クラスのメンバには、検索を行う Macth メソッドや Matches メソッド、置換を行う Replace メソッド等があります。
(2) Match クラス
Match クラスは、System.Text.RegularExpressions.Match 名前空間に存在し、正規表現の検索結果を格納する時等に用います。Match クラスは、1 回の正規表現検索に一致した結果を表します。正規表現パターンを入力文字列に繰り返し使用する場合は、MatchCollection クラスを利用します。
1.4.3 正規表現を用いた検索例 (ワイルドカード検索)
本項では、正規表現を用いたワイルドカード検索の例を解説します。
(1) フロー図
ワイルドカード検索は、検索文字列に * が存在する場合、その部分は 「任意の長さ」 かつ 「任意の文字」 が検索対象になります。そのため、直前の正規表現の 0 回以上の繰り返しを検索する * 構文を用います。* 構文の直前の文字には、[] 構文を用いて任意の英数字を [a-zA-Z0-9] で表現します。処理の流れとしては、まず、正規表現検索により検索文字列に * が存在する場合、その直前に [a-zA-Z0-9] を挿入します。そして、上記の処理によって作成された正規表現を用いて再検索を行います。本例のフロー図を図 4 に示します。

図 4 : ワイルドカード検索のフロー図
(2) ソースコード
ワイルドカード検索のソースコードを次に示します。
…
using System.Text.RegularExpressions; //名前空間のエイリアスを作成
…
Regex rSet1, rSet2; // 正規表現の設定
Match mSearch1, mSearch2; // 正規表現による検索
string sText1, sText2; // sText1 : 検索文字列 sText2 : 検索対象文字列
int iIndex = 0; // *の検索発見位置
sText1 = textBox1.Text; // 検索文字列
sText2 = textBox2.Text; // 検索対象文字列
rSet1 = new Regex("\\*",RegexOptions.IgnoreCase|RegexOptions.Compiled); // *の検索
for (mSearch1 = rSet1.Match(sText1); mSearch1.Success; mSearch1 = mSearch1.NextMatch())
{
iIndex = mSearch1.Groups[0].Index + iIndex; // iIndexの調整
sText1 = sText1.Insert(iIndex, "[a-zA-Z][a-zA-Z0-9]");// 正規表現の設定文字列の挿入
iIndex=19; // 挿入文字列分追加
}
rSet2 = new Regex(sText1,RegexOptions.IgnoreCase|RegexOptions.Compiled); // 検索
for (mSearch2 = rSet2.Match(sText2); mSearch2.Success; mSearch2 = mSearch2.NextMatch())
textBox3.Text = mSearch2.Groups[0].Value; // 検索結果の表示
1.5 文字列処理のプログラム例 (数式計算)
本節では、実際にプログラムを作成し、String クラス、StringBuilder クラス、正規表現を用いた文字列処理の習得を目指します。本例では、入力した四則演算の数式が正しいか判断し、計算するプログラムを作成します。
(1) フロー図
本プログラムのフロー図を図 5 に示します。

図 5 : 数式計算のフロー図
本プログラムでは、再帰呼び出しのアルゴリズムを利用します。再帰呼び出しとは、呼び出された関数内で再度同一の関数を呼び出す手法です。この手法は、繰り返し処理などの同一作業の繰り返しに大変役立ちます。
(2) エイリアスを作成
エイリアスは、参照するクラスの表記の負担を解消するため、using ステートメントを用いて作成します。
using System.Text.RegularExpressions; //名前空間のエイリアスを作成
using System.Text; //名前空間のエイリアスを作成
(3) 入力文字列のチェック
filter メソッドは、入力された文字列に誤りがないかをチェックします。チェック項目は、演算子の連続や最初と最後の文字、括弧の数等です。演算子等の検索には、正規表現を利用しています。また、括弧のチェックにおいては、括弧のカウント中に " (" と ") " の数の差を算出します。そして、" (" の数が ") " の数より少なくなった場合は、false 値を返します。
for (mSearch2 = rSet2.Match(sInputData); mSearch2.Success; mSearch2 = mSearch2.NextMatch())
{
if(sInputData[mSearch2.Groups[0].Index] == '(')
iCount1 = iCount1 + 1; // (のカウント
else
iCount2 = iCount2 + 1; // )のカウント
if(iCount1 - iCount2 < 0) // (と)の数の比較
return false; // (と)の数が異なる場合
}
if(iCount1 != iCount2) // (と)の数の比較
return false; // (と)の数が異なる場合
(4) 再帰呼び出しによる数式の分解
calculate_main メソッドでは、演算子と括弧の検索を行って数式の分解を行います。また、検索には、正規表現を利用しています。検索開始位置が 0 でない場合は、指定された開始位置から検索を行います。
// 再帰呼び出しの場合
else
{
mSearch = rSet.Match(sInputData); // 検索の実行
// iStart位置まで検索を実行
while (mSearch.Groups[0].Index != iStart )
{
// (が先頭の場合
if(sInputData[0] == '(' && iStart == 1)
{
iStart = 0;
break;
}
mSearch = mSearch.NextMatch(); // 検索の実行
}
正規表現による検索において、負の数を示すマイナスは、演算子として識別されます。そのため、負の数を扱う場合には、負を示すマイナスを除いて検索する必要があります。本プログラムでは、検索結果が負を示すマイナスの場合は、その次の演算子の検索を行います。そして、検索が成功した場合は、その結果を正しい演算子の位置として取得します。
if(i == 0)
{
// マイナスの数値の場合わけ
if(sInputData[iStart + 1] == '-')
mSearch = mSearch.NextMatch(); // 検索の実行
}
else
{
// マイナスの数値の場合わけ
if(mSearch.Groups[0].Index - iCount[0] == 1 &&
sInputData[mSearch.Groups[0].Index] != '(')
mSearch = mSearch.NextMatch(); // 検索の実行
}
if(mSearch.Success)
iCount[i] = mSearch.Groups[0].Index; // 発見位置の取得
else
iCaseflag = NOTFOUND; // 次に演算子などが無い場合
(5) 計算
calculate_sub メソッドでは、分割された数式をもとに計算を行います。そして、sInputData において、その計算結果と計算式の置き換えを行います。置き換えの例を図 6 に示します。また、文字列の置き換えには、StringBuilder クラスのReplace メソッドを利用します。

図 6:置き換えの例
sbTempData = sbTempData.Replace(sSearch, sTemp , iStart, sSearch.Length);
// 文字列の置き換え
sInputData = sbTempData.ToString(0, sbTempData.Length );
calculate_main(ref sInputData,0); // メイン関数の呼び出し
(6) 実行結果
本プログラムの実行結果を図 7 に示します。

図 7:実行結果
1.6 まとめ
本章では、Visual C# を用いて文字列処理について解説しました。Visual C# では、String クラス、StringBuilder クラス、正規表現等を利用して、様々な文字列処理を行うことが可能です。このことにより、システム開発の生産性および信頼性が向上することは言うまでもなく、より高度なシステムを開発することが可能になります。これからの連載を通じ、.NET Framework に用意された様々な便利なクラスとその利用法を紹介すると同時に、様々なアルゴリズムを体感できるような情報を提供していきたいと思います。
アルゴリズム入門
本連載は、テキスト処理、グラフィック処理、画像処理、知識情報処理などのアルゴリズムを Visual C# を用いて解説します。
著者略歴
田中 成典 (たなか しげのり)
| 1986 年 | 関西大学工学部土木工学科卒業 |
| 1988 年 | 関西大学大学院工学研究科 土木工学専攻博士課程前期課程修了 |
| 1996 年 | 博士 (工学) 授与,関西大学 |
| 1997 年 | 関西大学総合情報学部助教授 (現在に至る) |
| 主な著書: | やさしい C のはじめかた,オーム社,1993 年 |
| | 建設技術者のための知識情報処理の実践,関西大学出版部,1999 年 |
| | DirectX8,工学社,2001 年 |
| | ステップアップ XML,工学社,2002 年 |
| | Linux アプリケーション入門,森北出版,2002年 ほか |
中山 浩太郎 (なかやま こうたろう)
| 2001 年 3 月 | 関西大学総合情報学部総合情報学科卒業 |
| 2003 年 3 月 | 関西大学大学院総合情報学研究科 博士課程前期課程修了 |
| 2003 年 4 月 | 関西大学大学院総合情報学研究科 博士課程後期課程入学 (現在に至る) |
| 主な著書: | Web 工房シリーズ Perl の達人,森北出版,1999 年 |
| | 決定版 Visual Basic,共立出版,2000年 |
| | DirectX8,工学社,2001 年 |
| | Linux アプリケーション入門,森北出版,2002 年 |
| | ステップアップ Visual C# .NET 入門,工学社,2002 年 ほか |
中村 健二 (なかむら けんじ)
| 2000 年 4 月 | 関西大学総合情報学部総合情報学科入学 (現在に至る) |
| 主な著書: | DirectX8 & VC++ 3D の基礎とゲームの作り方,工学社,2002年 |
北川 悦司 (きたがわ えつじ)
| 2000 年 3 月 | 関西大学総合情報学部総合情報学科卒業 |
| 2002 年 3 月 | 関西大学大学院総合情報学研究科 博士課程前期課程修了 |
| 2002 年 4 月 | 関西大学大学院総合情報学研究科 博士課程後期課程入学 (現在に至る) |
| 主な著書: | Web 工房シリーズ Java の達人,森北出版,1999 年 |
| | デジカメ活用によるデジタル写真測量入門,森北出版,2000 年 |
| | ステップアップ XML 活用法,工学社,2002 年 |
上山 智士 (うえやま さとし)
| 2002 年 4 月 | 関西大学総合情報学部総合情報学科入学 (現在に至る) |
杉町 敏之 (すぎまち としゆき)
| 2003 年 3 月 | 関西大学総合情報学部総合情報学科卒業 |
| 2003 年 4 月 | 関西大学大学院総合情報学研究科入学 (現在に至る) |
| 主な著書: | ステップアップ Visual C# .NET 入門,工学社,2002 年 |
野中 一希 (のなか かずき)
| 2003 年 3 月 | 関西大学総合情報学部総合情報学科卒業 |
| 2003 年 4 月 | 関西大学大学院総合情報学研究科入学 (現在に至る) |
| 主な著書: | ステップアップ Visual C# .NET 入門,工学社,2002 年 |
|