【问题标题】:Can I use LINQ to strip repeating spaces from a string?我可以使用 LINQ 从字符串中去除重复的空格吗?
【发布时间】:2010-08-29 16:37:02
【问题描述】:

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

This  is a string with  repeating   spaces

LINQ 的最终表达方式是什么

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

【问题讨论】:

  • 有人问为什么是LINQ。我很欣赏 LINQ 可能不是最好的解决方案。尽管如此,这是对集合的操作,我有兴趣看到基于集合的方法。谢谢!
  • 你可以,并不意味着你应该!
  • 您的非 LINQ 解决方案会跳过第一个空格之后的所有内容。

标签: c# linq linq-to-objects


【解决方案1】:

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

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

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

【讨论】:

  • 你不觉得 Regex 对这个来说太严厉了吗?
  • @Michael Teper:就个人而言,我认为使用 LINQ 扩展方法来解决这个问题的其他解决方案是笨拙的。您可以用一条极短且易读的行来解决问题,或者您可以使用一种不太明显的方法,它需要 2 条或更多更长的行。哪个更有意义?
  • @Michael 一点也不,这是一个完美的,如果相当简单的正则表达式问题。
  • Dan:我认为这是对问题的根本误解。将问题想象为“如何从流中消除连续重复?”。正则表达式解决方案仅适用于一般问题的一个具体示例。
  • Dan:首先,Ani 的解决方案并没有想象中的那么好(见我的)。其次,鉴于程序解决方案是最好的,正则表达式也不是正确的解决方案!
【解决方案2】:
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());
}

【讨论】:

  • 酷,我没有意识到提供索引的 Where 重载。很高兴知道!
  • 此实现将删除所有开头的空格,但会留下一个尾随空格。简化 Where: index == 0 || 中的条件!char.IsWhiteSpace(text[index-1]) 应该保留一个前导空格。 +1
【解决方案3】:

由于似乎没有人给出令人满意的答案,我想出了一个。这是一个基于字符串的解决方案(.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.Replace(s, " +", " ")) 进行了基准测试,它们之间的时间相差不到几纳秒,因此与额外的正则表达式开销相比,额外的 LINQ 开销可以忽略不计。当我将它概括为删除所有连续的重复字符时,等效的正则表达式 (Regex.Replace(s, "(.)\\1+", "$1")) 比我的 LINQ 版本 (string.Join("", s.RemoveRepeatedElements())) 慢 3.5 倍

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

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 倍以上!

【讨论】:

  • @Gabe:在我看来,您的 LINQ 魔法有一个弱点。这种方法需要为s 创建三个单独的枚举器。这假设这些枚举器都将以相同的顺序遍历s。使用单个枚举器的方法对数据的假设较少(尽管显然我并不是说它是完美的)。
  • Dan:我希望s 总是以相同的顺序遍历——否则“连续重复”的概念有点没有意义。当然,如果这是一个问题(例如,s 是一个延迟生成的随机数列表),这就是 EnumerableEx.Memoize (community.bartdesmet.net/blogs/bart/archive/2010/01/07/…) 的用途。
  • @Gabe:假设您有一个随机数流,并且您希望在处理它们时跳过连续出现的相同值。这对我来说似乎是一个合理的场景。我想在这种情况下你可以使用Memoize。真的,只是我对使用三个枚举器来做你可以用一个来完成的事情持怀疑态度。拥有它们的成本似乎非常不确定。
  • Dan:如果您查看我帖子末尾的编辑,您会发现“额外”枚举器的成本可以忽略不计(因为他们很懒惰)。如果每个枚举器都制作了流的副本,那将是不可接受的开销,但由于每个枚举器只是一个带有指向字符串中当前位置的指针的对象,因此开销非常小。
  • @Gabe:但这完全取决于IEnumerable&lt;T&gt; 的实现!您可能在List&lt;T&gt;T[] 或类似的东西上对其进行了测试。在这种情况下,确保开销可以忽略不计。我只是说,对于像这个这样的通用解决方案,你不能假设创建一个枚举器总是很便宜。
【解决方案4】:

在实践中,我可能只会使用您的原始解决方案或正则表达式(如果您想要一个快速简单的解决方案)。使用 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,直到操作返回与作为参数相同的值。您可以将该操作视为一个通用循环 - 它非常有用,尽管我认为它太令人讨厌而无法包含在 .NET BCL 中。然后你可以写:

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

它不如你的原始版本高效,但除非有太多空格,否则它应该可以正常工作。

【讨论】:

  • @Tomas: 我真的很喜欢您将strange complicated theory 应用于编程问题并提出优雅的 C# 和 F# 解决方案的方式。出于这个原因,真的很喜欢你的real-world functional programming book。 +1 一个有趣的(如果可能有点过分;-) 解决方案!
  • @stakx:谢谢:-)!我同意这是最好的解决方案,但我无法抗拒......
【解决方案5】:

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

【讨论】:

  • 字符串字符的集合
  • @Jamiec:在语义上它是,但不是作为框架中的 IEnumerable 实现的。抱歉,缺乏精确性。
【解决方案6】:

Paul Creasey's answer 是要走的路。

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

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

更新

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

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

【讨论】:

  • 您实际上不必维护状态。您可以通过让迭代器使用Zip 函数为您维护状态来在功能上做到这一点(请参阅stackoverflow.com/questions/3595583/…)。显然,如果我在 Linq-to-Objects 中实现这一点,那么您的实现就是要走的路。例如,如果您需要它在 SQL Server 上运行,您的方法将不起作用。
  • @Gabe:我的意思是,这个解决方案需要 someone(或 something)保持状态;请注意,我以 Ani 的回答为例,因为状态是在字符串本身中维护的,所以很容易错过这一点。简单地观察您可以使用Zip 编写类似的解决方案并不会使这一点无效;它只是将状态维护移到其他地方。再次,我想向 OP 强调,“我想用 LINQ 执行此操作”的要求会在指定的要求和更合乎逻辑的方法之间产生冲突。
  • Dan:OP 明确表示这是一个“脑筋急转弯”,明确表示他不想要“更合乎逻辑”的方法。然后他说“这是对集合的操作,我有兴趣看到基于集合的方法”(意思是“流”,他说“集合”),强调他想要一种更通用的方法。
  • @Gabe:好的,关于评论的公平点(我无法跟踪 OP 所说的顺序)。但是,您如何从“脑筋急转弯”一词得出OP“显然”对“逻辑”方法不感兴趣的结论?对脑筋急转弯的逻辑答案是不可接受的吗?我有点认为他们是最好的......
  • Dan:当有人说“给定约束 X,解决问题 Y”时,您可以假设他们期望 Y 的逻辑答案使得 X 成立。我认为可以肯定地说,通常像“只需消除约束 X”这样的答案不是他们想要的,或者他们一开始就不会提到 X。您是否会回答代码高尔夫问题并批评答案太难理解和无法维护?
【解决方案7】:

拆分为列表,过滤,然后重新加入,2 行代码...

var test = "  Alpha      Beta     Tango    ";

var l = test.Split(' ').Where(s => !string.IsNullOrEmpty(s));
var result = string.Join(" ", l);

// result = "Alpha Beta Tango"

重构为扩展方法:

using Extensions;
void Main()
{
    var test = " Alpha    Beta   Tango    ";
    var result = test.RemoveRepeatedSpaces();
    // result = "Alpha Beta Tango";
}

static class Extentions 
{
    public static string RemoveRepeatedSpaces(this string s)
    {
        if (s == null) 
           return string.Empty;

        var l = s.Split(' ').Where(a => !string.IsNullOrEmpty(a));
        return string.Join(" ", l);
    }
}

【讨论】:

    猜你喜欢
    • 2012-04-05
    • 1970-01-01
    • 2012-09-01
    • 2017-08-29
    • 1970-01-01
    • 2010-12-07
    • 2014-02-13
    • 2019-09-10
    相关资源
    最近更新 更多