2010-05-19 2 views
30

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

EDIT: Я должен добавить, что я использую .NET 2.0.


Bounty обновление: Я буду катить это обратно после щедрот присуждается, но я хотел бы прояснить несколько вещей.

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

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

+0

Регулярное и быстрое выражение. Какой аспект вы пытаетесь оптимизировать, когда говорите «лучший способ»? Читаемость? Время? Использование памяти? –

+0

Я бы сказал, что читаемость будет самой важной в этом случае. – FunLovinCoder

+6

Считываемость редко приравнивается к регулярным выражениям –

ответ

20

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

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

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

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

выглядит хорошо для меня. –

+1

'\ s +' вместо '\ s *' было бы лучше, я думаю, –

+0

@ Правитель Салмана Криса, как и мой одинокий, недооцененный ответ. ; - ( –

1
string corrected = 
    System.Text.RegularExpressions.Regex.Replace(input, @"\n+", "\n"); 
+1

Если строка содержит пробельные символы, которые нужно удалить, вы можете изменить @ "\ n +" на @ "\ n \ s? \ n + " –

+0

@ Ник терпит неудачу в тестировании, и это тоже не работает. – Will

8

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

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

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

+0

нет метода IsNullOrWhitespace;) –

+0

@ Томас Левеске: орли? http://msdn.microsoft.com/en-us/library/system.string.isnullorwhitespace.aspx – dtb

+0

моя ошибка ... это новое в .NET 4.0, и у меня есть только локальная помощь для 3.5 –

0
char[] delimiters = new char[] { '\r', '\n' }; 
string[] lines = value.Split(delimiters, StringSplitOptions.RemoveEmptyEntries); 
string result = string.Join(Environment.NewLine, lines) 
+0

Как он обрабатывает большие файлы? – rds

13

с верхней части моей головы ...

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

получается так:

 
fdasdf 
asdf 
[tabs] 

[spaces] 

asdf 


в этом:

 
fdasdf 
asdf 
asdf 
+0

Что ?! нет любви к элегантному регулярному выражению? Я раздавлен. –

+0

Существует несколько разных способов написания этого регулярного выражения, но я думаю, что подход с регулярным выражением читаемый –

+1

+1. Элегантно, он также удалит вкладки и пробелы из конец другой не пустой строки, но это, вероятно, хорошо. Вам не нужен вариант «Multiline». –

15
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(); 
} 
+0

+1 Это хорошо, так как он должен хорошо масштабироваться для больших строк. –

+2

Не должно ли это быть 'if (line.Trim(). Length> 0) writer.WriteLine (строка)'? ОП не запрашивал, чтобы все строки были обрезаны в выходной строке. –

+0

@ Dan, хороший улов! Я исправил его –

1

Вот еще один вариант: используйте класс 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 является new in .NET 4.0.Если вы не имеете, что это тривиальное писать самостоятельно:

public static bool IsNullOrWhiteSpace(string text) { 
    return string.IsNullOrEmpty(text) || text.Trim().Length < 1; 
} 
+0

@ Адам: Ха, ничего себе, очень глупое заявление, которое я сделал там. Я не имел промежуточных * массивов *, поскольку метод 'string.Split' (спасибо). –

1

Я пойду с:

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

Попробуйте это.

string s = "Test1" + Environment.NewLine + Environment.NewLine + "Test 2"; 
Console.WriteLine(s); 

string result = s.Replace(Environment.NewLine, String.Empty); 
Console.WriteLine(result); 
+1

Это полностью не сработает. – SLaks

+0

Что делать, если я читаю файл, импортированный из системы unix, тогда мои окна Environment.NewLine не будут соответствовать новым строкам из файла. – felickz

-2
s = Regex.Replace(s, @"^[^\n\S]*\n", ""); 

[^\n\S] соответствует любому символу, который не является перевод строки или непробельным характер - так, любой символ пробела, за исключением \n. Но, скорее всего, единственные персонажи, которые вы должны беспокоиться о являются пробелы, символы табуляции и возврата каретки, так что это должно работать также:

s = Regex.Replace(s, @"^[ \t\r]*\n", ""); 

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

s = Regex.Replace(s, @"^[ \t\r]*\n?", ""); 
+1

Ничего из этого на самом деле не работает. Все они оставляют пробельные строки. – Will

3

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

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

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

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

Или в C# код, предоставленный 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 линиям стилей! Проверьте это! Я считаю, что это будет самый правильный ответ, хотя более простое выражение передаст ваш заданный тест на награду, это регулярное выражение проходит более сложные условия.

EDIT: @ Указывает на потенциальный недостаток в последнем совпадении шаблонов указанного выше регулярного выражения, поскольку он не будет соответствовать разрыву строк, содержащему пробел в конце тестовой строки.Итак, давайте изменим этот последний шаблон на этот:

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

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

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

EDIT # 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. пробелы в конце файла
+0

@ Это должно удовлетворить ваши требования с помощью одного Regex.Replace. – BenSwayne

+0

Ой, это выглядит как много работы, но оно также терпит неудачу, когда в конце строки есть смешанные символы новой строки и пробелы. Например, эта строка '' one \ r \ n \ r \ ntwo \ r \ n \ t \ r \ n \ r \ n "' будет '' одной \ r \ ntwo \ r \ n "' после замены , – Will

+0

@ Будем делать редактирование, чтобы устранить эту ошибку *. Регулярное много работы и не проверено/определено как самый быстрый способ удалить строки из строки, но это то, о чем вы просили. Однострочное регулярное выражение. – BenSwayne

2

В ответ на Баунти Уилла, который ожидает решения, которое принимает "test\r\n \r\nthis\r\n\r\n" и выходы "test\r\nthis", я придумал решение, которое делает использование atomic grouping (ака Nonbacktracking Subexpressions на 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]); 
} 

EDIT: Для решения вопроса о шаблоне неисправного убирать текст со смесью пробелов и переводов строк, я добавил \s* до последнего чередования части регулярного выражения. Моя предыдущая модель была избыточной, и я понял, что \s* будет обрабатывать оба случая.

+0

Приятная попытка, но это не идеально. Он не работает при смешивании пробелов и строк новой строки ближе к концу строки. '' one \ r \ n \ r \ ntwo \ r \ n \ t \ r \ n \ r \ n "' все равно будет иметь эту новую строку в конце. – Will

+0

@ Будем благодарны за отзывы. Я обновил шаблон и образец кода для решения нового тестового примера. Попробуйте. Я также очистил почту в отношении того, что было съедено, и решил сохранить часть '(?! \ B)' в '^ \ s + (?! \ B)', поскольку я думаю, что это ближе к духу запрашивать и поддерживать пробелы, где существует действительный символ. –

+1

Ааа, гораздо лучше. Я потрачу некоторое время сегодня (стилизация и) профилирование и запуск тестовых примеров на нем. Благодарю. – Will

1

В ответ на Баунти Уилла здесь является суб 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; 
+0

Er, я могу использовать регулярные выражения Perl-compat ... но я не знаком с Perl. Можете ли вы просто уточнить, что такое регулярное выражение? Я думаю, что я это высказал, но я хочу быть уверенным. Благодарю. (отредактируйте), да, например, я только что узнал о s/operator. Кроме того, если есть какие-либо параметры конфигурации (многострочные и т. Д.) (Редактировать редактировать) * Также * Это должен быть PCRE 5; 7 не отрежет. \ R является слишком новым дополнением. – Will

+0

@Will: Посмотрите мое обновление. – Toto

+0

Хммм, я не могу заставить его работать. Он * делает * удаляет пустые строки и любые завершающие символы новой строки, но также обрезает последний символ без пробелов в каждой строке. Возможно, проблема с конверсией. Любой шанс вы можете дать мне регулярное выражение без какого-либо синтаксиса perl? * – Will

0

Вот что-то простое, если работать с каждой отдельной линии ...

(^\s+|\s+|^)$ 
0

Eh. Ну, после всего этого, я не мог найти тот, который ударил бы по всем углам, которые я мог бы выяснить. Ниже мое последнее колдовство из регулярных выражений, что раздевает

  1. Всех пустых строк с начала строки
    • Не включая любые пробела в начале первой непробельной линии
  2. Все пустые строки после первой строки без пробелов и до последней строки без пробелов
    • Опять же, сохранение всех пробелов в начале любой строки без пробелов
  3. Все пустые строки после последней непробельного линии, включая последний символ новой строки

(? < = (\ г \ п) | ^) \ с * \ г \ п | \ г \ п \ s * $

, который по существу говорит:

  • Сразу после
    • начало строки ИЛИ
    • конец последней строки
  • матч, как много смежных пробелов, как это возможно, что заканчивается символом новой строки *
  • ИЛИ
  • матч новой строки и столько же смежный пробелы, как это возможно, что заканчивается в конце строки

Первая половина ловит все пробельные в начале строки до первая строка без пробелов или все пробелы между не-пробельными строками.Вторая половина помешает оставшимся пробелам в строке, включая новую строку новой строки, отличной от строки.

Спасибо всем, кто пытался помочь; ваши ответы помогли мне продумать все, что мне нужно было учитывать при сопоставлении.

* (Это регулярное выражение рассматривает новую строку быть \r\n, и поэтому должны быть скорректированы в зависимости от источника строки. Параметры не должны быть установлены для того, чтобы запустить матч.)

1

, если его только пробелы почему вы не используете метод # строки C

string yourstring = "A O P V 1.5"; 
    yourstring.Replace(" ", string.empty); 

результат будет "AOPV1.5"

0

Строка Extension

public static string UnPrettyJson(this string s) 
{ 
    try 
    { 
     // var jsonObj = Json.Decode(s); 
     // var sObject = Json.Encode(value); dont work well with array of strings c:['a','b','c'] 

     object jsonObj = JsonConvert.DeserializeObject(s); 
     return JsonConvert.SerializeObject(jsonObj, Formatting.None); 
    } 
    catch (Exception e) 
    { 
     throw new Exception(
      s + " Is Not a valid JSON ! (please validate it in http://www.jsoneditoronline.org)", e); 
    } 
} 
2

Не хорошо. Я использовал бы это с помощью JSON.net:

var o = JsonConvert.DeserializeObject(prettyJson); 
new minifiedJson = JsonConvert.SerializeObject(o, Formatting.None); 
Смежные вопросы