我可以使用LINQ去除字符串中的重复空格吗?

一个快速的脑筋急转弯:给一个字符串

This is a string with repeating spaces 

LINQ表达的最终结果是什么

 This is a string with repeating spaces 

谢谢!

作为参考,这是一种非LINQ方式:

 private static IEnumerable RemoveRepeatingSpaces(IEnumerable text) { bool isSpace = false; foreach (var c in text) { if (isSpace && char.IsWhiteSpace(c)) continue; isSpace = char.IsWhiteSpace(c); yield return c; } } 

由于似乎没有人给出满意的答案,我想出了一个。 这是一个基于字符串的解决方案(.Net 4):

 public static string RemoveRepeatedSpaces(this string s) { return s[0] + string.Join("", s.Zip( s.Skip(1), (x, y) => x == y && y == ' ' ? (char?)null : y)); } 

但是,这只是从序列中删除重复元素的一般情况,所以这里是通用版本:

 public static IEnumerable RemoveRepeatedElements( this IEnumerable s, T dup) { return s.Take(1).Concat( s.Zip( s.Skip(1), (x, y) => x.Equals(y) && y.Equals(dup) ? (object)null : y) .OfType()); } 

当然,这只是一个更具体的函数版本,它从输入流中删除所有连续的重复项:

 public static IEnumerable RemoveRepeatedElements(this IEnumerable s) { return s.Take(1).Concat( s.Zip( s.Skip(1), (x, y) => x.Equals(y) ? (object)null : y) .OfType()); } 

显然你可以用第二个函数实现第一个函数:

 public static string RemoveRepeatedSpaces(this string s) { return string.Join("", s.RemoveRepeatedElements(' ')); } 

顺便说一句,我将我的最后一个函数与正则表达式版本( Regex.Replace(s, " +", " ") )进行了基准测试,它们彼此相距纳秒,因此与额外的正则表达式开销相比,额外的LINQ开销可以忽略不计。 当我推广它以删除所有连续的重复字符时,等效的正则表达式( Regex.Replace(s, "(.)\\1+", "$1") )比我的LINQ版本慢3.5倍string.Join("", s.RemoveRepeatedElements()) )。

我也尝试了“理想的”程序解决方案:

 public static string RemoveRepeatedSpaces(string s) { StringBuilder sb = new StringBuilder(s.Length); char lastChar = '\0'; foreach (char c in s) if (c != ' ' || lastChar != ' ') sb.Append(lastChar = c); return sb.ToString(); } 

这比正则表达式快5倍!

这不是linq类型的任务,使用正则表达式

 string output = Regex.Replace(input," +"," "); 

当然,您可以使用linq将其应用于字符串集合。

 public static string TrimInternal(this string text) { var trimmed = text.Where((c, index) => !char.IsWhiteSpace(c) || (index != 0 && !char.IsWhiteSpace(text[index - 1]))); return new string(trimmed.ToArray()); } 

在实践中,我可能只是使用您的原始解决方案或正则表达式(如果您想要一个快速简单的解决方案)。 使用lambda函数的一种令人讨厌的方法是定义一个固定点运算符:

 T FixPoint(T initial, Func f) { T current = initial; do { initial = current; current = f(initial); } while (initial != current); return current; } 

这会一直重复调用操作f ,直到操作返回与参数相同的值。 您可以将操作视为一个通用循环 – 它非常有用,但我认为它太过于令人讨厌,无法包含在.NET BCL中。 然后你可以写:

 string res = FixPoint(original, s => s.Replace(" ", " ")); 

它没有原始版本那么高效,但除非空间太多,否则应该可以正常工作。

根据定义,Linq与可枚举(即集合,列表,数组)相关。 您可以将您的字符串转换为char的集合并选择非空格,但这肯定不是Linq的工作。

Paul Creasey的答案是要走的路。

如果您想将制表符视为空白,请使用:

 text = Regex.Replace(text, "[ |\t]+", " "); 

更新

Hasan和Ani都提出了在满足“使用LINQ”要求的同时解决这个问题的最合理的方法。 但请注意,这些解决方案涉及通过索引访问字符串中的字符。

LINQ方法的精神在于它可以应用于任何可枚举的序列。 因为任何合理有效的解决方案都需要保持某种状态(使用Ani和Hasan的解决方案很容易错过这个事实,因为状态已经在字符串本身内维护),接受任何项目序列的通用方法可能会进行使用过程代码实现起来要简单得多。

然后,可以将该过程代码抽象看起来像LINQ样式方法的方法。 但我不建议从一开始就以“我想在此解决方案中使用LINQ”的态度解决这样的问题,因为它会对您的代码施加非常尴尬的限制。

对于它的价值,这是我如何实现一般的想法。

 public static IEnumerable StripConsecutives(this IEnumerable source, T value, IEqualityComparer comparer) { // null-checking omitted for brevity using (var enumerator = source.GetEnumerator()) { if (enumerator.MoveNext()) { yield return enumerator.Current; } else { yield break; } T prev = enumerator.Current; while (enumerator.MoveNext()) { T current = enumerator.Current; if (comparer.Equals(prev, value) && comparer.Equals(current, value)) { // This is a consecutive occurrence of value -- // moving on... } else { yield return current; } prev = current; } } }