Эффективное удаление всех пустых строк из многострочной строки - PullRequest
30 голосов
/ 19 мая 2010

В C #, как лучше всего удалять пустые строки, то есть строки, содержащие только пробел из строки? Я рад использовать Regex, если это лучшее решение.

РЕДАКТИРОВАТЬ: я должен добавить, что я использую .NET 2.0.


Обновление щедрости : я вернусь к этому после того, как награда будет вручена, но я хотел уточнить некоторые вещи.

Во-первых, любое регулярное выражение для Perl 5 будет работать. Это не ограничивается разработчиками .NET. Название и теги были отредактированы, чтобы отразить это.

Во-вторых, хотя я привел краткий пример в информации о наградах, это не только тест, который вы должны выполнить. Ваше решение должно удалить все строки, которые состоят только из пробелов, , а также последней новой строки . Если есть строка, которая после выполнения вашего регулярного выражения заканчивается на "/ r / n" или любых пробельных символов , она завершится неудачно.

Ответы [ 19 ]

20 голосов
/ 19 мая 2010

Если вы хотите удалить строки, содержащие пробелы (табуляции, пробелы), попробуйте:

string fix = Regex.Replace(original, @"^\s*$\n", string.Empty, RegexOptions.Multiline);

Редактировать (для @Will): простейшим решением для обрезки завершающих строк новой строки будет использование TrimEnd в результирующей строке, например ::

string fix =
    Regex.Replace(original, @"^\s*$\n", string.Empty, RegexOptions.Multiline)
         .TrimEnd();
16 голосов
/ 19 мая 2010
string outputString;
using (StringReader reader = new StringReader(originalString)
using (StringWriter writer = new StringWriter())
{
    string line;
    while((line = reader.ReadLine()) != null)
    {
        if (line.Trim().Length > 0)
            writer.WriteLine(line);
    }
    outputString = writer.ToString();
}
13 голосов
/ 19 мая 2010

с макушки головы ...

string fixed = Regex.Replace(input, "\s*(\n)","$1");

превращает это:

fdasdf
asdf
[tabs]

[spaces]  

asdf


в это:

fdasdf
asdf
asdf
8 голосов
/ 19 мая 2010

Использование LINQ:

var result = string.Join("\r\n",
                 multilineString.Split(new string[] { "\r\n" }, ...None)
                                .Where(s => !string.IsNullOrWhitespace(s)));

Если вы имеете дело с большими входами и / или непоследовательными окончаниями строк, вам следует использовать StringReader и вместо этого выполнить вышеупомянутую старую школу с циклом foreach.

3 голосов
/ 09 декабря 2011

Хорошо, этот ответ соответствует уточненным требованиям, указанным в награде:

Мне также нужно удалить любые завершающие символы новой строки, и мой Regex-fu терпит неудачу. Моя награда достается любому, кто может дать мне регулярное выражение, которое проходит этот тест: StripWhitespace ("test \ r \ n \ r \ nthis \ r \ n \ r \ n") == "тест \ г \ nЭто"

Так вот ответ:

(?<=\r?\n)(\s*$\r?\n)+|(?<=\r?\n)(\r?\n)+|(\r?\n)+\z

Или в коде C #, предоставленном @Chris Schmich:

string fix = Regex.Replace("test\r\n \r\nthis\r\n\r\n", @"(?<=\r?\n)(\s*$\r?\n)+|(?<=\r?\n)(\r?\n)+|(\r?\n)+\z", string.Empty, RegexOptions.Multiline);

Теперь давайте попробуем это понять. Здесь есть три дополнительных шаблона, которые я готов заменить на string.empty.

  1. (?<=\r?\n)(\s*$\r?\n)+ - соответствует одной неограниченной строке, содержащей только пробел и которой предшествует разрыв строки (но не соответствует первым предыдущим разрывам строки).
  2. (?<=\r?\n)(\r?\n)+ - соответствует одной неограниченной пустой строке без содержимого, которому предшествует разрыв строки (но не соответствует первым предыдущим разрывам строки).
  3. (\r?\n)+\z - соответствует одному неограниченному разрыву строки в конце тестируемой строки (завершающие разрывы строк, как вы их называли)

Это отлично удовлетворяет вашему тесту! Но также удовлетворяет как \r\n, так и \n стилям разрыва строки! Проверьте это! Я полагаю, что это будет самый правильный ответ, хотя более простое выражение пройдет указанный вами тест на вознаграждение, это регулярное выражение пройдет более сложные условия.

РЕДАКТИРОВАТЬ: @Will указал потенциальный недостаток в последнем сопоставлении с шаблоном вышеупомянутого регулярного выражения в том, что он не будет соответствовать нескольким разрывам строк, содержащих пробел в конце тестовой строки. Итак, давайте изменим этот последний шаблон на этот:

\b\s+\z \ b - это граница слова (начало или конец слова), \ s + - один или несколько символов пробела, \ z - конец тестовой строки (конец «файла») , Так что теперь он будет соответствовать любому ассортименту пробелов в конце файла, включая вкладки и пробелы в дополнение к возврату каретки и разрывам строк. Я протестировал оба тестовых примера @Will.

Итак, все вместе теперь должно быть:

(?<=\r?\n)(\s*$\r?\n)+|(?<=\r?\n)(\r?\n)+|\b\s+\z

РЕДАКТИРОВАТЬ # 2: Хорошо, есть еще один возможный случай @Wil обнаружил, что последнее регулярное выражение не распространяется. В этом случае входные данные имеют разрывы строк в начале файла перед любым содержимым. Итак, давайте добавим еще один шаблон, соответствующий началу файла.

\A\s+ - \A соответствует началу файла, \s+ соответствует одному или нескольким символам пробела.

Так что теперь у нас есть:

\A\s+|(?<=\r?\n)(\s*$\r?\n)+|(?<=\r?\n)(\r?\n)+|\b\s+\z

Итак, теперь у нас есть четыре шаблона для сопоставления:

  1. пробел в начале файла,
  2. избыточные разрывы строк, содержащие пробелы, (например: \r\n \r\n\t\r\n)
  3. избыточные разрывы строк без содержимого (например: \r\n\r\n)
  4. пробел в конце файла
3 голосов
/ 01 октября 2014

не хорошо. Я бы использовал этот, используя JSON.net:

var o = JsonConvert.DeserializeObject(prettyJson);
new minifiedJson = JsonConvert.SerializeObject(o, Formatting.None);
2 голосов
/ 09 декабря 2011

В ответ на щедрость Уилла, которая ожидает решение, которое принимает "test\r\n \r\nthis\r\n\r\n" и выводит "test\r\nthis", я предложил решение, которое использует атомарную группировку (он же Nonbacktracking) Субэкспрессия на MSDN). Я рекомендую прочитать эти статьи для лучшего понимания того, что происходит. В конечном итоге атомная группа помогла сопоставить висячие символы новой строки, которые в противном случае остались позади.

Используйте RegexOptions.Multiline с этим шаблоном:

^\s+(?!\B)|\s*(?>[\r\n]+)$

Вот пример с некоторыми тестами, в том числе с комментариями Уилла к другим постам, а также с моими собственными.

string[] inputs = 
{
    "one\r\n \r\ntwo\r\n\t\r\n \r\n",
    "test\r\n \r\nthis\r\n\r\n",
    "\r\n\r\ntest!",
    "\r\ntest\r\n ! test",
    "\r\ntest \r\n ! "
};
string[] outputs = 
{
    "one\r\ntwo",
    "test\r\nthis",
    "test!",
    "test\r\n ! test",
    "test \r\n ! "
};

string pattern = @"^\s+(?!\B)|\s*(?>[\r\n]+)$";

for (int i = 0; i < inputs.Length; i++)
{
    string result = Regex.Replace(inputs[i], pattern, "",
                                  RegexOptions.Multiline);
    Console.WriteLine(result == outputs[i]);
}

РЕДАКТИРОВАТЬ: Чтобы решить проблему с шаблоном, который не может очистить текст смесью пробелов и новых строк, я добавил \s* к последней чередующейся части регулярного выражения. Мой предыдущий шаблон был избыточен, и я понял, что \s* будет обрабатывать оба случая.

1 голос
/ 09 декабря 2011

В ответ на щедрость Уилла есть подпрограмма Perl, которая дает правильный ответ на тестовый пример:

sub StripWhitespace {
    my $str = shift;
    print "'",$str,"'\n";
    $str =~ s/(?:\R+\s+(\R)+)|(?:()\R+)$/$1/g;
    print "'",$str,"'\n";
    return $str;
}
StripWhitespace("test\r\n \r\nthis\r\n\r\n");

выход:

'test

this

'
'test
this'

Чтобы не использовать \R, замените его на [\r\n] и поменяйте местами альтернативу. Этот дает тот же результат:

$str =~ s/(?:(\S)[\r\n]+)|(?:[\r\n]+\s+([\r\n])+)/$1/g;

Нет необходимости в специальной настройке и поддержке нескольких линий. Тем не менее, вы можете добавить s флаг, если это обязательно.

$str =~ s/(?:(\S)[\r\n]+)|(?:[\r\n]+\s+([\r\n])+)/$1/sg;
1 голос
/ 19 мая 2010

Я пойду с:

  public static string RemoveEmptyLines(string value) {
    using (StringReader reader = new StringReader(yourstring)) {
      StringBuilder builder = new StringBuilder();
      string line;
      while ((line = reader.ReadLine()) != null) {
        if (line.Trim().Length > 0)
          builder.AppendLine(line);
      }
      return builder.ToString();
    }
  }
1 голос
/ 19 мая 2010

Вот еще один вариант: используйте класс StringReader. Преимущества: один проход по строке не создает промежуточных массивов.

public static string RemoveEmptyLines(this string text) {
    var builder = new StringBuilder();

    using (var reader = new StringReader(text)) {
        while (reader.Peek() != -1) {
            string line = reader.ReadLine();
            if (!string.IsNullOrWhiteSpace(line))
                builder.AppendLine(line);
        }
    }

    return builder.ToString();
}

Примечание: метод IsNullOrWhiteSpace является новым в .NET 4.0 . Если у вас этого нет, написать тривиально самостоятельно:

public static bool IsNullOrWhiteSpace(string text) {
    return string.IsNullOrEmpty(text) || text.Trim().Length < 1;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...