6

我有一个包含多个句子的字符串。如何将每个句子中第一个单词的第一个字母大写。类似于 word 中的段落格式。

例如,“这是一些代码。代码是 C#。” 输出必须是“这是一些代码。代码是 C#”。

一种方法是根据 '.' 拆分字符串。然后将第一个字母大写,然后重新加入。

有更好的解决方案吗?

4

5 回答 5

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(); });
}

正则表达式使用 (?<=) 构造(零宽度正向后视)将捕获限制为仅在字符串开头之前的 az 个字符或您想要的标点符号。在位中,[.;:]您可以添加您想要的额外内容(例如[.;:?."]添加 ? 和 " 字符。

这也意味着,您的 MatchEvaluator 不必进行任何不必要的字符串连接(出于性能原因,您希望避免这种情况)。

从性能的角度来看,其他回答者之一提到的有关使用 RegexOptions.Compiled 的所有其他内容也与此相关。不过,静态 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.48 秒而不是 1.68 秒)。

然而,有趣的是,如果通过静态 Regex.Replace 方法完成,两者都慢了大约 80%,而且我的解决方案比 Ahmad 的慢。

于 2010-01-25T22:16:50.147 回答
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 种模式的更新示例。第一个显示所有标点符号如何影响大小写。第二个展示了如何通过使用类减法来挑选某些标点符号类别。它使用所有标点符号,同时删除特定的标点符号组。第三个类似于第二个,但使用不同的组。

MSDN 链接没有说明某些标点符号类别指的是什么,所以这里有一个细分:

  • P:所有标点符号(包括以下所有类别)
  • 个人计算机:下划线_
  • :破折号-
  • 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();
}

请注意,“Dash”没有使用最后一个模式大写,而是在一个新行上。使其大写的一种方法是使用该RegexOptions.Multiline选项。尝试上面的代码片段,看看它是否符合您想要的结果。

另外,为了举例,我没有在上面的循环中使用 RegexOptions.Compiled 。要同时使用这两个选项或它们:RegexOptions.Compiled | RegexOptions.Multiline.

于 2010-01-25T22:24:07.133 回答
4

你有几个不同的选择:

  1. 您拆分字符串,大写然后重新加入的方法
  2. 使用正则表达式来执行表达式的替换(这对于大小写可能有点棘手)
  3. 编写一个 C# 迭代器,它遍历每个字符并IEnumerable<char>在句点后的第一个字母大写生成一个新的。可以提供流式解决方案的好处。
  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 的建议,我决定尝试收益回报。只是为了好玩。

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 回答