今回はC#での文字列操作をしてみようと思います。
Unityでゲームを作る時にも文字列を操作する事は多々ありますね。
例えば、RPGゲーム内で資料を見る事が出来たとしたら、その資料内からある文字列を使って文書を検索出来る機能を取り付けたり、
タイピングソフトで「ちゃ」の入力を練習したい時に、問題の中に「ちゃ」を含む文字列を問題を抽出して出題したり、
ただ単に変数に入れた文字列に別の文字列を連結してテキストフィールドに表示したりといった時です。
今回のサンプルもUnity付属のMonoDevelopを使用して確認します。
文字列に関して
C#での文字列は
1 2 3 | string stringValue = "テスト"; |
という風にstringを付けて宣言します。
string型の変数は参照型の変数になります。
文字列を一旦インスタンス化するとその値は変更されません。
その為、
1 2 3 4 5 6 | string stringValue1 = "テスト"; string stringValue2 = "テスト2"; string stringValue1 = stringValue1 + stringValue2; |
とした場合最初に作成したstringValue1のインスタンスは破棄され新しく作成されることになります。
その為、文字列の連結をする毎にインスタンス化をする事になる為、大量の文字列を扱う時はStringBuilder等を使って操作します。
これに関しては後でやります。
それではいくつか文字列の操作を見ていきましょう。
サンプル用に二つの文字列を作成しておきます。
1 2 3 4 | string stringValue1 = "あいうえお かきくけこ さしすせそ たちつてと かきくけこ さしすせそ"; string stringValue2 = "aiueo,kakikukeko,sashisuseso,tachituteto,kakikukeko,sashisuseso"; |
インスタンスメソッド
まずはインスタンスメソッドの操作関連を見ていきます。
+演算子で文字列を連結
文字列の連結は+演算子を使ってする事が出来ます。
1 2 3 | Debug.Log (1 + "テスト" + stringValue1); |
1テストあいうえお かきくけこ さしすせそ たちつてと かきくけこ さしすせそ が表示されます。
Containsメソッド
Containsメソッドを使うと、その文字列を含んでいるかどうかがわかります。
1 2 3 | Debug.Log(stringValue1.Contains ("さしすせそ")); |
↑の例ではtrueが表示されます。
Lengthメソッド
Lengthメソッドを使うと文字列の長さを返します。
1 2 3 | Debug.Log(stringValue1.Length); |
↑の例では35が表示されます。
IndexOfメソッド
IndexOfメソッドは文字列が最初に見つかった位置を返します。
1 2 3 | Debug.Log(stringValue1.IndexOf ("さしすせそ")); |
↑の例では12が表示されます。
LastIndexOfメソッド
LastIndexOfメソッドは文字列が最後に見つかった位置を返します。
1 2 3 | Debug.Log(stringValue1.LastIndexOf ("さしすせそ")); |
↑の例では30が表示されます。
Insertメソッド
Insertメソッドを使うと文字列中に文字列を挿入する事が出来ます。
1 2 3 | Debug.Log(stringValue1.Insert (stringValue1.IndexOf ("さしすせそ"), "なにぬねの ")); |
↑の例では あいうえお かきくけこ なにぬねの さしすせそ たちつてと かきくけこ さしすせそ が表示されます。
PadLeftメソッド
PadLeftメソッドを使うと文字列を右寄せし、指定した文字数に足らない部分に空白を入れます。
1 2 3 | Debug.Log (("12345").PadLeft (10)); |
↑の例では 12345となります(Unityのコンソールで確認してください)。
PadRightメソッド
PadRightメソッドを使うと文字列を左寄せし、指定した文字数に足らない部分に空白を入れます。
1 2 3 | Debug.Log (("12345").PadRight (10) + ":" + "a"); |
↑の例では 12345 :a となります(Unityのコンソールで確認してください)。
Removeメソッド
Removeメソッドを使うとインデックスで指定した位置から指定した文字数分を削除します。
1 2 3 | Debug.Log (stringValue1.Remove (stringValue1.IndexOf ("さしすせそ"), 6)); |
↑の例では「さしすせそ」が最初に見つかった位置から6文字文を削除します。
「 さしすせそ」という空白文字を含む6文字を削除しますので、あいうえお かきくけこ たちつてと かきくけこ さしすせそ が表示されます。
StartsWithメソッド
StartsWithメソッドを使うと文字列が指定した文字列で始まるかどうかを調べる事が出来ます。
1 2 3 | Debug.Log(stringValue1.StartsWith ("あいうえお")); |
↑の例ではTrueが表示されます。
EndsWithメソッド
EndsWithメソッドを使用すると文字列が指定した文字列で終わるかどうかを調べる事が出来ます。
1 2 3 | Debug.Log(stringValue1.EndsWith ("さしすせそ")); |
↑の例ではTrueが表示されます。
Replaceメソッド
Replaceメソッドを使うと文字列を書き換える事が出来ます。
1 2 3 | Debug.Log (stringValue1.Replace ("かきくけこ", "kakikukeko")); |
↑の例では あいうえお kakikukeko さしすせそ たちつてと kakikukeko さしすせそ が表示されます。
Splitメソッド
Splitメソッドを使用すると文字列を分割文字を使って配列を作成する事が出来ます。
1 2 3 4 5 6 | string[] stringArray = stringValue2.Split (','); foreach (var value in stringArray) { Debug.Log (value); } |
↑の例では分割文字に,を指定し分割するので、aiueo kakikukeko sashisuseso tachituteto kakikukeko sashisuseso の要素を持つstringArrayが作成されます。
ToUpperメソッド
ToUpperメソッドを使用すると文字列を大文字にする事が出来ます。
1 2 3 | Debug.Log(stringValue2.ToUpper ()); |
↑の例では AIUEO,KAKIKUKEKO,SASHISUSESO,TACHITUTETO,KAKIKUKEKO,SASHISUSESO が表示されます。
ToLowerメソッド
ToLowerメソッドを使用すると文字列を小文字にする事が出来ます。
1 2 3 | Debug.Log("ABCD".ToLower ()); |
↑の例では abcd が表示されます。
SubStringメソッド
SubStringメソッドは第1引数で指定したインデックス値から第2引数で指定した文字数分を取得出来ます。
1 2 3 | Debug.Log(stringValue1.Substring (stringValue1.IndexOf ("あいうえお"), stringValue1.IndexOf ("たちつてと"))); |
↑の例では、「あいうえお」が見つかった位置から「たちつてと」が見つかった位置(そこまでの文字数)を取得するので、
あいうえお かきくけこ さしすせそ
が表示されます。
Trim、TrimStart、TrimEnd
Trimを使うと文字列の前後、TrimStartは前、TrimEndは後にある空白文字を削除します。
1 2 3 4 5 6 7 8 | // 前後の空白を取り除く Debug.Log(" あいうえお かきくけこ ".Trim ()); // 前の空白を取り除く Debug.Log(" あいうえお かきくけこ ".TrimStart ()); // 後ろの空白を取り除く Debug.Log(" あいうえお かきくけこ ".TrimEnd ()); |
クラスメソッド
クラスメソッドを使った文字列の処理を見ていきます。
Compareメソッド
Compareメソッドを使用すると文字列が同一かどうかを判定する事が出来ます。
同一であると判定されれば0が返ります。
第3引数でCultureInfo、第4引数で比較オプションを指定出来ます。
1 2 3 4 5 6 | // 文字列の比較、ひらがなとカタカナを同一と見なす。合ってれば0を表示 Debug.Log(String.Compare ("あいうえお", "アイウエオ", new CultureInfo ("ja-JP"), CompareOptions.IgnoreKanaType)); // 文字列の比較、全角と半角の区別と空白や記号を無視した比較の両方の条件を有効にする Debug.Log(String.Compare ("アイウエオ", " アイウエオ &|", new CultureInfo ("ja-JP"), CompareOptions.IgnoreWidth | CompareOptions.IgnoreSymbols)); |
↑の例では最初はひらがなとカタカナの比較でも同一と判定出来るようにし、次のCompareでは全角と半角を無視、空白や記号等を無視して同一と判定出来るようにしています。
結果は両方0が表示されます。
Formatメソッド
Formatメソッドを使用すると文字列をフォーマットして返す事が出来ます。
1 2 3 4 | string formatString = String.Format ("第1文字列は{0}、\n第2文字列は{1}", stringValue1, stringValue2); Debug.Log (formatString); |
第1引数では”の中にフォーマットを記述し、{}の中の数字と第2引数以降の数字が対応します。
↑の例では
第1文字列はあいうえお かきくけこ さしすせそ たちつてと かきくけこ さしすせそ、
第2文字列はaiueo,kakikukeko,sashisuseso,tachituteto,kakikukeko,sashisuseso
が表示されます。
IsNullOrEmptyメソッド
IsNullOrEmptyメソッドを使うとその文字列がNullまたは空文字でないか判定する事が出来ます。
1 2 3 4 5 6 7 8 9 10 11 12 | // 文字列がNullか空文字か string emptyString = ""; string nullString = null; if (String.IsNullOrEmpty (emptyString)) { Debug.Log ("emptyStringはNullか空文字"); } if (String.IsNullOrEmpty (nullString)) { Debug.Log ("nullStringはNullか空文字"); } |
↑の例では emptyStringはNullか空文字 nullStringはNullか空文字 が表示されます。
Concatメソッド
Concatメソッドを使用すると文字列を連結する事が出来ます。
1 2 3 | Debug.Log(String.Concat (stringValue1, stringValue2)); |
↑の例では、あいうえお かきくけこ さしすせそ たちつてと かきくけこ さしすせそaiueo,kakikukeko,sashisuseso,tachituteto,kakikukeko,sashisuseso が表示されます。
Joinメソッド
Joinメソッドを使用すると配列を分割文字で繋げて文字列を作成する事が出来ます。
1 2 3 4 5 | string[] stringArray2 = new string[] {"test1", "test2", "test3"}; string stringValue3 = String.Join ("", stringArray2); Debug.Log(stringValue3); |
↑の例では分割文字に空文字を指定してstringArray2配列の要素を連結し、文字列を作成しています。
test1test2test3
が表示されます。
ここまでのサンプル
ここまでのサンプルを載せておきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 | using UnityEngine; using System.Collections; using System; using System.Globalization; using System.Collections.Generic; public class StringTest : MonoBehaviour { // Use this for initialization void Start () { string stringValue1 = "あいうえお かきくけこ さしすせそ たちつてと かきくけこ さしすせそ"; string stringValue2 = "aiueo,kakikukeko,sashisuseso,tachituteto,kakikukeko,sashisuseso"; /* インスタンスメソッド */ // 数値や変数と+記号で足す Debug.Log (1 + "テスト" + stringValue1); // さしすせそ が存在するかどうか Debug.Log(stringValue1.Contains ("さしすせそ")); // 文字列の長さ Debug.Log(stringValue1.Length); // 文字列中で指定した文字列が最初に見つかった位置 Debug.Log(stringValue1.IndexOf ("さしすせそ")); // 文字列中で指定した文字列が最後に見つかった位置 Debug.Log(stringValue1.LastIndexOf ("さしすせそ")); // 文字列中に文字列を挿入する Debug.Log(stringValue1.Insert (stringValue1.IndexOf ("さしすせそ"), "なにぬねの ")); // 指定した文字数になるまで文字を右寄せし、左側に空白を入れる Debug.Log (("12345").PadLeft (10)); // 指定した文字数になるまで文字を左寄せし、右側に空白を入れる Debug.Log (("12345").PadRight (10) + ":" + "a"); // 指定したインデックスから指定した文字列数分を削除する Debug.Log (stringValue1.Remove (stringValue1.IndexOf ("さしすせそ"), 6)); // あいうおえ で始まっているかどうか Debug.Log(stringValue1.StartsWith ("あいうえお")); // さしすせそ で終わっているかどうか Debug.Log(stringValue1.EndsWith ("さしすせそ")); // 配列の要素の書き換え Debug.Log (stringValue1.Replace ("かきくけこ", "kakikukeko")); // ,で分割し配列に入れる string[] stringArray = stringValue2.Split (','); foreach (var value in stringArray) { Debug.Log (value); } // 文字列を大文字に変換 Debug.Log(stringValue2.ToUpper ()); // 文字列を小文字に変換 Debug.Log("ABCD".ToLower ()); // 最初のインデックスから指定した文字列数分を取得 Debug.Log(stringValue1.Substring (stringValue1.IndexOf ("あいうえお"), stringValue1.IndexOf ("たちつてと"))); // 前後の空白を取り除く Debug.Log(" あいうえお かきくけこ ".Trim ()); // 前の空白を取り除く Debug.Log(" あいうえお かきくけこ ".TrimStart ()); // 後ろの空白を取り除く Debug.Log(" あいうえお かきくけこ ".TrimEnd ()); /* クラスメソッド */ // 文字列の比較、ひらがなとカタカナを同一と見なす。合ってれば0を表示 Debug.Log(String.Compare ("あいうえお", "アイウエオ", new CultureInfo ("ja-JP"), CompareOptions.IgnoreKanaType)); // 文字列の比較、全角と半角の区別と空白や記号を無視した比較の両方の条件を有効にする Debug.Log(String.Compare ("アイウエオ", " アイウエオ &|", new CultureInfo ("ja-JP"), CompareOptions.IgnoreWidth | CompareOptions.IgnoreSymbols)); // フォーマットを指定して出力 string formatString = String.Format ("第1文字列は{0}、\n第2文字列は{1}", stringValue1, stringValue2); Debug.Log (formatString); // 文字列がNullか空文字か string emptyString = ""; string nullString = null; if (String.IsNullOrEmpty (emptyString)) { Debug.Log ("emptyStringはNullか空文字"); } if (String.IsNullOrEmpty (nullString)) { Debug.Log ("nullStringはNullか空文字"); } // 文字列の連結 Debug.Log(String.Concat (stringValue1, stringValue2)); // 配列やを連結して一つの文字列を作成 string[] stringArray2 = new string[] {"test1", "test2", "test3"}; string stringValue3 = String.Join ("", stringArray2); Debug.Log(stringValue3); } } |
長い文章内の文字列の書き換え、削除、検索サンプル
文字列操作メソッドを使った長い文章内の文字列の書き換えと、指定した文字列の削除、指定したキーワードが見つかった場所から数文字を表示するサンプルを作成してみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | using UnityEngine; using System.Collections; using System; using System.Collections.Generic; public class StringSample : MonoBehaviour { // Use this for initialization void Start () { string sampleString = "おはようございます。今日も良いお天気でよかったですね。\n" + "一昨日は雨で3000歩しか歩けませんでした。\n" + "昨日は、散歩中に犬に追いかけられるという不運にも見舞われながらも、一日6000歩を達成する事が出来ました。\n" + "今日は少し頑張って8000歩を目指しつつ、無理をしないように歩けたらいいなと思います。\n"; string oldWord = "今日"; string replaceWord = "明日"; string resultString = sampleString; // 該当する文字列を変更 Debug.Log ("変更前の文字列: " + sampleString); resultString = resultString.Replace (oldWord, replaceWord); Debug.Log ("変更後の文字列: " + resultString); resultString = sampleString; while (resultString.Contains (oldWord)) { var index1 = resultString.IndexOf (oldWord); resultString = resultString.Remove (index1, oldWord.Length); } Debug.Log ("指定した文字列を削除: " + resultString); List<string> resultList = new List<string> (); resultString = sampleString; // 取り出す文字数 var takeNum = 10; // 検索位置 var index2 = 0; // oldWordで検索した場所からtakeNumの文字数分を取得し、リストに保存 while((index2 = resultString.IndexOf (oldWord, index2)) != -1) { resultList.Add (resultString.Substring (index2, takeNum)); index2 += takeNum; } foreach (var item in resultList) { Debug.Log (item); } } } |
sampleStringには初期の文章を入れておきます。
この文章の中で「今日」という文字列を「明日」という文字列に書き換えます。
Replaceメソッドを使えば文章中にある「今日」という文字列を全て「明日」に簡単に変更出来ます。
Replaceメソッドの戻り値は書き換えた後の文字列が返って来るので、それをresultStringに入れ直す事で書き換えた文字列が入ります。
文章中から指定した文字列を削除するのも簡単で、まずは検索する文字列のインデックス値をIndexOfメソッドで調べます。
Removeメソッドを使用してインデックス値(index1)から検索した文字列の長さ分を削除し、それをresultStringに入れ直します。
whileループを使ってresultString内に検索文字列が存在している間は繰り返します。
ここでまちがってsampleStringを指定してしまうと、みなさん大好き無限ループが待っています(謎)。
最後が検索した文字列で始まる場所から数文字を取りだし、それを出力する処理です。
まずは検索文字列で見つかった場所のインデックス値(index2)をindex2の位置から調べます(-1が返ってきたら検索出来なかった時)。
whileの条件式がちょっと複雑になっていますが、これは文章中で一旦検索した後の位置から次の検索をしたい為の処理です。
index2は最初は0に設定しているので、文章の最初から検索します。
例えば検索した文字列が2の位置であれば、条件式内でindex2に2が入ります。
その後Substringメソッドを使ってindex2の位置からtakeNumの文字数分を取りだしリストに登録します。
その後index2にtakeNumの値を足し、検索した文字列の最後の位置にインデックス値を移動させます。
例えば「今日は昨日の明日、今日は一昨日の明後日」という文章中から「今日」という検索文字列を指定し実行すると
最初はindex2が0で検索し、見つかるのが最初なのでwhile条件式でindex2に0が入ります。
takeNumを5(5文字取りだす)としていた場合はSubstringを使って「今日」を含む5文字である「今日は昨日」という文字列がリストに登録されます。
1 2 3 | index2 += takeNum; |
で、index2が5になります。
その為whileの条件式に戻った時は「今日は昨日」の後の「の」から検索文字列の「今日」を探すことになります。
次に今日が見つかるのは、index2が9の時です。
そこからtakeNumの5文字文を取りだし、「今日は一昨」という文字列がリストに登録されます。
このスクリプトは文章中に「今日今日」と並んでいた場合2回目の今日は最初の検索で引っかかった後に、takeNumが5なのでインデックスを進めてしまい無視されてしまう事になります。
自分で作っておいてなんなんですが、そもそも何をやりたいスクリプトかはよくわかりません・・・・(^_^;)
文字列操作を適当に使ってみたサンプルだと思って頂ければ・・・・。
文字列操作にStringBuilderクラスを使う
最初の方で言及しましたが、string型の変数は文字列を連結する毎に毎回新しいインスタンスを生成しています。
1 2 3 4 5 | string stringValue1 = "てすと"; stringValue1 = "テスト"; stringValue1 += "1"; |
↑の例では3回インスタンス化が行われています。
これだと文字列操作が大量になると、それだけインスタンス化処理が多くなる為、処理が遅くなります。
文字列操作を多くする場合はStringBuilderクラスをインスタンス化し利用した方が処理が早くなります。
1 2 3 4 5 6 7 | StringBuilder stringBuilder = new StringBuilder(); // 文字列の追加 stringBuilder.Append("テスト"); // 文字列に変換 string stringValue = stringBuilder.ToString(); |
StringBuilderはAppendメソッドを使って文字列を追記する事が出来ます。
InsertやRemove、Replaceといったメソッドも用意されているので、通常の文字列と同じように操作する事が出来ます。
操作を加えたものを文字列に変換するにはToStringメソッドを使用します。
+演算子とStringBuilderの処理速度の比較
試しに大量の文字列を+演算子とStringBuilderを使った処理で計測してみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | using UnityEngine; using System.Collections; using System.Text; using System.Collections.Generic; using System; public class StringBuilderTest : MonoBehaviour { // Use this for initialization void Start () { StringBuilder stringBuilder = new StringBuilder (); string stringValue = String.Empty; int loopNum = 100; float startTime; // +演算子で文字列を追加する処理 startTime = Time.realtimeSinceStartup; for (int i = 0; i < loopNum; i++) { stringValue += "test" + i + "\n"; } Debug.Log ("+演算子の処理にかかった時間 " + (Time.realtimeSinceStartup - startTime)); // StringBuilderを使った文字列の追加処理 startTime = Time.realtimeSinceStartup; for (int i = 0; i < loopNum; i++) { stringBuilder.Append ("test" + i + "\n"); } string stringValue2 = stringBuilder.ToString (); Debug.Log ("StringBuilderの処理にかかった時間 " + (Time.realtimeSinceStartup - startTime)); } } |
String.Emptyで空文字を入れる事が出来ます(””でも同じです)。
loopNumでループ回数を指定し、+演算子とStringBuilderそれぞれを使って文字列を連結していきます。
Time.realtimeSinceStartupはUnityのクラスでゲーム開始からのリアルタイムを取得出来ます。
試しにloopNumを100にしてそれぞれの実行時間を計測してみます。
+演算子は0.0002872944秒
StringBuilderは0.0001244545秒
と若干StringBuilderが速いですね。
次はloopNumを10000にして実行してみます。
+演算子は2.371711秒
StringBuilderは0.01119804秒
となりました。
+演算子は10000回文字列を足すと2秒以上かかってますが、StringBuilderを使うと0.01秒ぐらいで終わってますね。
大量に文字列操作をする時はStringBuilderを使った方が良い事がわかりました。