6

複数の文を含む文字列があります。すべての文の最初の単語の最初の文字を大文字にする方法. Word の段落書式のようなもの。

例 ."これはコードです。コードは C# です。" 出力は、"これはコードです。コードは C# です" である必要があります。

1 つの方法は、「.」に基づいて文字列を分割することです。最初の文字を大文字にしてから、再び結合します。

より良い解決策はありますか?

4

5 に答える 5

5

句読点カテゴリを使用して .!?" などを指定する必要を回避する正規表現ソリューションを次に示しますが、それがニーズをカバーしているか、明示的に設定しているかを確認する必要があります。「サポートされている Unicode 一般カテゴリ」の「P」カテゴリを読んでください。MSDN 文字クラス ページにある " セクション。

string input = @"this is some code. the code is in C#? it's great! In ""quotes."" after quotes.";
string pattern = @"(^|\p{P}\s+)(\w+)";

// compiled for performance (might want to benchmark it for your loop)
Regex rx = new Regex(pattern, RegexOptions.Compiled);

string result = rx.Replace(input, m => m.Groups[1].Value
                                + m.Groups[2].Value.Substring(0, 1).ToUpper()
                                + m.Groups[2].Value.Substring(1));

\p{P}クラスを使用しない場合は、次のように自分で文字を指定する必要があります。

string pattern = @"(^|[.?!""]\s+)(\w+)";

編集:以下は、3 つのパターンを示す更新された例です。1 つ目は、すべての句読点が大文字と小文字にどのように影響するかを示しています。2 つ目は、クラス減算を使用して特定の句読点カテゴリを選択する方法を示しています。特定の句読点グループを削除しながら、すべての句読点を使用します。3 番目は 2 番目と似ていますが、異なるグループを使用しています。

MSDN のリンクでは、句読点のカテゴリの一部が何を参照しているかが詳しく説明されていないため、内訳を次に示します。

  • P : すべての句読点 (以下のすべてのカテゴリを含む)
  • PC : アンダースコア_
  • Pd : ダッシュ-
  • Ps : 開き括弧、括弧、および中括弧( [ {
  • Pe : 閉じ括弧、括弧、および中括弧) ] }
  • Pi : 最初の一重/二重引用符 (MSDN によると、「使用方法によっては Ps/Pe のように動作する可能性があります」)
  • Pf : 最後の一重/二重引用符 (MSDN Pi ノートが適用されます)
  • Po : コンマ、コロン,、セミコロン、スラッシュなどのその他の:句読点、、、、、;\/

これらのグループが結果に与える影響を注意深く比較してください。これにより、高度な柔軟性が得られます。これが望ましくないと思われる場合は、前に示したように文字クラスで特定の文字を使用できます。

string input = @"foo ( parens ) bar { braces } foo [ brackets ] bar. single ' quote & "" double "" quote.
dash - test. Connector _ test. Comma, test. Semicolon; test. Colon: test. Slash / test. Slash \ test.";

string[] patterns = { 
    @"(^|\p{P}\s+)(\w+)", // all punctuation chars
    @"(^|[\p{P}-[\p{Pc}\p{Pd}\p{Ps}\p{Pe}]]\s+)(\w+)", // all punctuation chars except Pc/Pd/Ps/Pe
    @"(^|[\p{P}-[\p{Po}]]\s+)(\w+)" // all punctuation chars except Po
};

// compiled for performance (might want to benchmark it for your loop)
foreach (string pattern in patterns)
{
    Console.WriteLine("*** Current pattern: {0}", pattern);
    string result = Regex.Replace(input, pattern,
                            m => m.Groups[1].Value
                                 + m.Groups[2].Value.Substring(0, 1).ToUpper()
                                 + m.Groups[2].Value.Substring(1));
    Console.WriteLine(result);
    Console.WriteLine();
}

最後のパターンを使用すると、「ダッシュ」は大文字ではなく、新しい行にあることに注意してください。大文字にする 1 つの方法は、RegexOptions.Multilineオプションを使用することです。上記のスニペットを試して、目的の結果が得られるかどうかを確認してください。

また、例のために、上記のループでは RegexOptions.Compiled を使用しませんでした。両方のオプションまたはそれらを一緒に使用するには: RegexOptions.Compiled | RegexOptions.Multiline.

于 2010-01-25T22:24:07.133 に答える
5

私の意見では、潜在的に複雑なルールベースの文字列の照合と置換に関しては、正規表現ベースのソリューションよりも優れたものはありません (非常に読みにくいという事実にもかかわらず!)。私の意見では、これが最高のパフォーマンスとメモリ効率を提供します。これがどれほど高速であるかに驚かれることでしょう。

入力文字列、正規表現パターン、および MatchEvaluator デリゲートを受け入れる Regex.Replace オーバーロードを使用します。MatchEvaluator は、Matchオブジェクトを入力として受け取り、文字列の置換を返す関数です。

コードは次のとおりです。

public static string Capitalise(string input)
{
  //now the first character
  return Regex.Replace(input, @"(?<=(^|[.;:])\s*)[a-z]",
    (match) => { return match.Value.ToUpper(); });
}

正規表現は (?<=) コンストラクト (ゼロ幅の正の後読み) を使用して、文字列の先頭が前にある a ~ z 文字、または必要な句読点のみにキャプチャを制限します。[.;:]ビットでは、必要なものを追加できます (たとえば、[.;:?."]? および " 文字を追加します。

これは、MatchEvaluator が不必要な文字列の結合を行う必要がないことも意味します (パフォーマンス上の理由からこれは避けたいと考えています)。

RegexOptions.Compiled の使用について他の回答者の 1 人が言及した他のすべてのものは、パフォーマンスの観点からも関連しています。ただし、静的な Regex.Replace メソッドは、非常によく似たパフォーマンス上の利点を提供します (辞書検索が追加されるだけです)。

私が言うように-ここでの他の非正規表現ソリューションのいずれかがより適切に機能し、同じくらい高速である場合、私は驚くでしょう.

編集

このソリューションを Ahmad のソリューションと比較しました。彼は、ルックアラウンドは自分のやり方で行うよりも効率が悪い可能性があると指摘しました。

これが私が行った大まかなベンチマークです。

public string LowerCaseLipsum
{
  get
  {
    //went to lipsum.com and generated 10 paragraphs of lipsum
    //which I then initialised into the backing field with @"[lipsumtext]".ToLower()
    return _lowerCaseLipsum;
  }
 }
 [TestMethod]
 public void CapitaliseAhmadsWay()
 {
   List<string> results = new List<string>();
   DateTime start = DateTime.Now;
   Regex r = new Regex(@"(^|\p{P}\s+)(\w+)", RegexOptions.Compiled);
   for (int f = 0; f < 1000; f++)
   {
     results.Add(r.Replace(LowerCaseLipsum, m => m.Groups[1].Value
                      + m.Groups[2].Value.Substring(0, 1).ToUpper()
                           + m.Groups[2].Value.Substring(1)));
   }
   TimeSpan duration = DateTime.Now - start;
   Console.WriteLine("Operation took {0} seconds", duration.TotalSeconds);
 }

 [TestMethod]
 public void CapitaliseLookAroundWay()
 {
   List<string> results = new List<string>();
   DateTime start = DateTime.Now;
   Regex r = new Regex(@"(?<=(^|[.;:])\s*)[a-z]", RegexOptions.Compiled);
   for (int f = 0; f < 1000; f++)
   {
     results.Add(r.Replace(LowerCaseLipsum, m => m.Value.ToUpper()));
   }
   TimeSpan duration = DateTime.Now - start;
   Console.WriteLine("Operation took {0} seconds", duration.TotalSeconds);
 }

リリース ビルドでは、私のソリューションは Ahmad のソリューションよりも約 12% 高速でした (1.68 秒に対して 1.48 秒)。

ただし、興味深いことに、静的な Regex.Replace メソッドを使用した場合、どちらも約 80% 遅くなり、私のソリューションは Ahmad のソリューションよりも遅くなりました。

于 2010-01-25T22:16:50.147 に答える
4

いくつかのオプションがあります。

  1. 文字列を分割し、大文字にしてから再結合するアプローチ
  2. 正規表現を使用して式の置換を実行します (ケースによっては少しトリッキーになる可能性があります)。
  3. 各文字を反復処理しIEnumerable<char>、ピリオドの後の最初の文字を大文字にして new を生成する C# イテレータを作成します。ストリーミング ソリューションの利点を提供する場合があります。
  4. 各文字をループし、ピリオドの直後に表示される文字を大文字にします (空白は無視されます)。StringBuffer を使用すると、これが簡単になります。

以下のコードは反復子を使用しています。

public static string ToSentenceCase( string someString )
{
  var sb = new StringBuilder( someString.Length );
  bool wasPeriodLastSeen = true; // We want first letter to be capitalized
  foreach( var c in someString )
  {
      if( wasPeriodLastSeen && !c.IsWhiteSpace ) 
      {
          sb.Append( c.ToUpper() );
          wasPeriodLastSeen = false;         
      }        
      else
      {
          if( c == '.' )  // you may want to expand this to other punctuation
              wasPeriodLastSeen = true;
          sb.Append( c );
      }
  }

  return sb.ToString();
}
于 2010-01-25T21:45:09.017 に答える
2

理由はわかりませんが、LBushkin の提案に基づいて、yield returnを試してみることにしました。楽しみのためだけに。

static IEnumerable<char> CapitalLetters(string sentence)
        {
            //capitalize first letter
            bool capitalize = true;
            char lastLetter;
            for (int i = 0; i < sentence.Length; i++)
            {
                lastLetter = sentence[i];
                yield return (capitalize) ? Char.ToUpper(sentence[i]) : sentence[i];


                if (Char.IsWhiteSpace(lastLetter) && capitalize == true)
                    continue;

                capitalize = false;
                if (lastLetter == '.' || lastLetter == '!') //etc
                    capitalize = true;
            }
        }

使用するには:

string sentence = new String(CapitalLetters("this is some code. the code is in C#.").ToArray());
于 2010-01-25T22:15:18.513 に答える
1
  1. StringBuffer で作業を行います。
  2. 全体を小文字にします。
  3. 先頭文字を大文字にしてループします。
  4. ToString を呼び出します。
于 2010-01-25T21:43:45.723 に答える