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

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

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

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

有更好的解决方案吗?

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

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

这是代码:

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

正则表达式使用(?<=)构造(零宽度正向后观)将捕获限制为仅在字符串开头之前的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 results = new List(); 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 results = new List(); DateTime start = DateTime.Now; Regex r = new Regex(@"(?<=(^|[.;:])\s*)[az]", 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%,我的解决方案比艾哈迈德慢。

这是一个使用标点符号类别的正则表达式解决方案,以避免必须指定。!?“等等,尽管您应该检查它是否满足您的需求或明确设置它们。阅读”支持的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 :所有标点符号(包括以下所有类别)
  • 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(); } 

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

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

你有几个不同的选择:

  1. 你分裂字符串,大写然后重新加入的方法
  2. 使用正则表达式来执行表达式的替换(对于大小写可能有点棘手)
  3. 编写一个C#迭代器,迭代每个字符并产生一个新的IEnumerable ,其中大写字母后面的第一个字母。 可以提供流媒体解决方案的好处。
  4. 循环遍历每个char和大写字母后一段时间后出现的那些(忽略空格) – 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(); } 

我不知道为什么,但我决定根据LBushkin的建议,试一试收益率 。 纯娱乐。

 static IEnumerable 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()); 
  1. 在StringBuffer中完成工作。
  2. 小写整个事情。
  3. 循环和大写前导字符。
  4. 调用ToString。