代码之家  ›  专栏  ›  技术社区  ›  Michael Teper Ryan Lee

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

  •  4
  • Michael Teper Ryan Lee  · 技术社区  · 14 年前

    一个快速的脑筋急转弯:给一根线

    This  is a string with  repeating   spaces
    

    林克最终会得到什么

    This is a string with repeating spaces
    

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

    private static IEnumerable<char> RemoveRepeatingSpaces(IEnumerable<char> text)
    {
      bool isSpace = false;
      foreach (var c in text)
      {
        if (isSpace && char.IsWhiteSpace(c)) continue;
    
        isSpace = char.IsWhiteSpace(c);
        yield return c;
      }
    }
    
    6 回复  |  直到 14 年前
        1
  •  3
  •   Gabe Timothy Khouri    14 年前

    因为似乎没有人给出满意的答案,所以我想出了一个。下面是一个基于字符串的解决方案(.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<T> RemoveRepeatedElements<T>(
                                 this IEnumerable<T> 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<T>());
    }
    

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

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

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

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

    顺便说一句,我将上一个函数与regex版本进行了基准测试( Regex.Replace(s, " +", " ") )而且它们之间的距离都在纳秒以内,所以与额外的正则表达式开销相比,额外的LINQ开销可以忽略不计。当我将它推广到删除所有连续的重复字符时,等价的regex( Regex.Replace(s, "(.)\\1+", "$1") )曾经 慢3.5倍 比我的LINQ版本( 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();
    }
    

        2
  •  15
  •   Paul Creasey    14 年前

    这不是linq类型的任务,请使用regex

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

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

        3
  •  7
  •   Ani    14 年前
    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());
    }
    
        4
  •  2
  •   Tomas Petricek    14 年前

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

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

    这一直在呼唤行动 f 重复执行,直到操作返回与作为参数得到的值相同的值为止。你可以把这个操作看作是一个广义的循环——它非常有用,不过我想它太古怪了,不能包含在.NETBCL中。然后你可以写:

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

        5
  •  0
  •   VdesmedT    14 年前

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

        6
  •  0
  •   Community CDub    8 年前

    Paul Creasey's answer

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

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

    更新 :

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

    LINQ方法的精神是它可以应用于任何可枚举序列。因为这个问题的任何合理有效的解决方案都需要维护某种状态(使用Ani和Hasan的解决方案,很容易忽略这个事实,因为状态已经在字符串本身中维护了),接受任何项目序列的通用方法很可能更易于使用过程代码实现。

    抽象的 变成一种方法 当然,就像LINQ风格的方法。但我不建议一开始就抱着“我想在这个解决方案中使用LINQ”的态度来解决这样的问题,因为这会给代码带来非常尴尬的限制。

    值得一提的是,以下是我如何实现这个总体想法。

    public static IEnumerable<T> StripConsecutives<T>(this IEnumerable<T> source, T value, IEqualityComparer<T> 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;
            }
        }
    }