Могу ли я использовать LINQ для удаления повторяющихся пробелов из строки? - PullRequest
4 голосов
/ 29 августа 2010

Быстрый тизер мозга: дана строка

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

Ответы [ 6 ]

14 голосов
/ 29 августа 2010

Это не задача типа linq, используйте регулярное выражение

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

Конечно, вы можете использовать linq, чтобы применить это к коллекции строк.

6 голосов
/ 29 августа 2010
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());
}
3 голосов
/ 30 августа 2010

Так как никто, кажется, не дал удовлетворительного ответа, я придумал один. Вот строковое решение (.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")) было в 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();
}

Это более чем в 5 раз быстрее, чем регулярное выражение!

2 голосов
/ 29 августа 2010

На практике я бы, вероятно, просто использовал ваше оригинальное решение или регулярные выражения (если вы хотите быстрое и простое решение).Гики-подход, использующий лямбда-функции, заключался бы в определении оператора с фиксированной точкой:

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("  ", " "));

Он не так эффективен, как ваша оригинальная версия, но если не будет слишком много пробелов, он будет работать нормально.

0 голосов
/ 29 августа 2010

Linq по определению относится к перечислимому (т.е. коллекции, списки, массивы).Вы можете преобразовать вашу строку в коллекцию char и выбрать не пробел, но это определенно не работа для Linq.

0 голосов
/ 29 августа 2010

Ответ Пола Криси - путь.

Если вы хотите рассматривать вкладки как пробельные символы, используйте:

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

UPDATE

Наиболее логичный способ решения этой проблемы при соблюдении требования "использование LINQ" был предложен как Хасан , так и Ани . Однако обратите внимание, что эти решения включают в себя доступ к символу в строке по индексу.

Суть подхода LINQ заключается в том, что его можно применять к любой перечисляемой последовательности. Поскольку любое достаточно эффективное решение этой проблемы требует поддержания некоторого состояния (с помощью решений Ани и Хасана этот факт легко пропустить, так как состояние уже поддерживается внутри самой строки), общий подход, который принимает любую последовательность элементов, вероятно, будет гораздо проще реализовать с использованием процедурного кода.

Этот процедурный код затем можно абстрагировать в метод, который выглядит как метод в стиле 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;
        }
    }
}
...