使用C#格式化字符串中的句子

Alw*_*mer 6 c# string formatting paragraph text-segmentation

我有一个包含多个句子的字符串.如何将每个句子中第一个单词的首字母大写.像单词中的段落格式.

例如,"这是一些代码.代码在C#中."输出必须是"这是一些代码.代码在C#中".

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

有更好的解决方案吗?

And*_*tan 5

在我看来,当谈到可能复杂的基于规则的字符串匹配和替换时 - 你不可能比基于Regex的解决方案好得多(尽管事实上它们很难阅读!).在我看来,这提供了最佳的性能和内存效率 - 你会惊讶于它的速度有多快.

我将使用Regex.Replace重载,它接受输入字符串,正则表达式模式和MatchEvaluator委托.MatchEvaluator是一个接受Match对象作为输入并返回字符串替换的函数.

这是代码:

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

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

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

其他一个回答者提到的关于使用RegexOptions.Compiled的所有其他内容从性能的角度来看也是相关的.静态Regex.Replace方法确实提供了非常相似的性能优势(只有一个额外的字典查找).

就像我说的 - 如果这里的任何其他非正则表达式解决方案能够更好地工作并且速度更快,我会感到惊讶.

编辑

已经把这个解决方案对抗艾哈迈德了,因为他非常正确地指出,环顾可能效率低于他的方式.

这是我做的粗略基准:

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);
 }
Run Code Online (Sandbox Code Playgroud)

在发布版本中,我的解决方案比Ahmad快约12%(1.48秒而不是1.68秒).

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


Ahm*_*eed 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));
Run Code Online (Sandbox Code Playgroud)

如果您决定不使用\p{P}该类,则必须自己指定字符,类似于:

string pattern = @"(^|[.?!""]\s+)(\w+)";
Run Code Online (Sandbox Code Playgroud)

编辑:下面是一个演示3种模式的更新示例.第一个显示所有标点如何影响套管.第二部分展示了如何使用类减法来选择和选择某些标点符号类别.它在删除特定标点符号组时使用所有标点符号.第三个类似于第二个但使用不同的组.

MSDN链接没有说明一些标点类别所指的内容,所以这里是一个细分:

  • P:所有标点符号(包括以下所有类别)
  • Pc:下划线_
  • Pd:破折号-
  • Ps:左括号,括号和括号( [ {
  • Pe:右括号,括号和括号) ] }
  • Pi:初始单/双引号(MSDN称它"可能表现得像Ps/Pe,具体取决于用法")
  • Pf:最终单/双引号(MSDN Pi备注适用)
  • :其他标点符号诸如逗号,冒号,分号和斜线,,:,;,\,/

仔细比较这些组的结果如何影响.这应该给你很大的灵活性.如果这似乎不合适,那么您可以在字符类中使用特定字符,如前所示.

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();
}
Run Code Online (Sandbox Code Playgroud)

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

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