Como você divide uma sequência de linhas múltiplas em linhas?
Eu sei assim
var result = input.Split("\n\r".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
parece um pouco feio e perde linhas vazias. Existe uma solução melhor?
Como você divide uma sequência de linhas múltiplas em linhas?
Eu sei assim
var result = input.Split("\n\r".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
parece um pouco feio e perde linhas vazias. Existe uma solução melhor?
Respostas:
Se parecer feio, basta remover a ToCharArray
chamada desnecessária .
Se você deseja dividir por um \n
ou por \r
, você tem duas opções:
Use uma matriz literal - mas isso fornecerá linhas vazias para finais de linha no estilo do Windows \r\n
:
var result = text.Split(new [] { '\r', '\n' });
Use uma expressão regular, conforme indicado por Bart:
var result = Regex.Split(text, "\r\n|\r|\n");
Se você deseja preservar linhas vazias, por que você diz explicitamente ao C # para jogá-las fora? ( StringSplitOptions
parâmetro) - use em seu StringSplitOptions.None
lugar.
Environment.NewLine
é um não-ir para mim. De fato, de todas as soluções possíveis, eu prefiro a que usa expressões regulares, pois somente ela lida com todas as plataformas de origem corretamente.
StringSplitOptions.RemoveEmptyEntries
.
Isso funciona muito bem e é mais rápido que o Regex:
input.Split(new[] {"\r\n", "\r", "\n"}, StringSplitOptions.None)
É importante ter o "\r\n"
primeiro na matriz para que seja considerado como uma quebra de linha. O acima fornece os mesmos resultados que qualquer uma dessas soluções Regex:
Regex.Split(input, "\r\n|\r|\n")
Regex.Split(input, "\r?\n|\r")
Só que o Regex é 10 vezes mais lento. Aqui está o meu teste:
Action<Action> measure = (Action func) => {
var start = DateTime.Now;
for (int i = 0; i < 100000; i++) {
func();
}
var duration = DateTime.Now - start;
Console.WriteLine(duration);
};
var input = "";
for (int i = 0; i < 100; i++)
{
input += "1 \r2\r\n3\n4\n\r5 \r\n\r\n 6\r7\r 8\r\n";
}
measure(() =>
input.Split(new[] {"\r\n", "\r", "\n"}, StringSplitOptions.None)
);
measure(() =>
Regex.Split(input, "\r\n|\r|\n")
);
measure(() =>
Regex.Split(input, "\r?\n|\r")
);
Resultado:
00: 00: 03.8527616
00: 00: 31.8017726
00: 00: 32.5557128
e aqui está o método de extensão:
public static class StringExtensionMethods
{
public static IEnumerable<string> GetLines(this string str, bool removeEmptyLines = false)
{
return str.Split(new[] { "\r\n", "\r", "\n" },
removeEmptyLines ? StringSplitOptions.RemoveEmptyEntries : StringSplitOptions.None);
}
}
Uso:
input.GetLines() // keeps empty lines
input.GetLines(true) // removes empty lines
[\r\n]{1,2}
\n\r
ou \n\n
como quebra de linha única, o que não está correto.
Hello\n\nworld\n\n
um caso extremo? É claramente uma linha com texto, seguida por uma linha vazia, seguida por outra linha com texto, seguida por uma linha vazia.
Você pode usar o Regex.Split:
string[] tokens = Regex.Split(input, @"\r?\n|\r");
Editar: adicionado |\r
à conta de terminadores de linha Mac (mais antigos).
\r
como final de linha.
Se você deseja manter linhas vazias, remova as StringSplitOptions.
var result = input.Split(System.Environment.NewLine.ToCharArray());
Eu tive essa outra resposta, mas essa, com base na resposta de Jack , é significativamente mais rápida, pode ser preferida, pois funciona de forma assíncrona, embora um pouco mais lenta.
public static class StringExtensionMethods
{
public static IEnumerable<string> GetLines(this string str, bool removeEmptyLines = false)
{
using (var sr = new StringReader(str))
{
string line;
while ((line = sr.ReadLine()) != null)
{
if (removeEmptyLines && String.IsNullOrWhiteSpace(line))
{
continue;
}
yield return line;
}
}
}
}
Uso:
input.GetLines() // keeps empty lines
input.GetLines(true) // removes empty lines
Teste:
Action<Action> measure = (Action func) =>
{
var start = DateTime.Now;
for (int i = 0; i < 100000; i++)
{
func();
}
var duration = DateTime.Now - start;
Console.WriteLine(duration);
};
var input = "";
for (int i = 0; i < 100; i++)
{
input += "1 \r2\r\n3\n4\n\r5 \r\n\r\n 6\r7\r 8\r\n";
}
measure(() =>
input.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None)
);
measure(() =>
input.GetLines()
);
measure(() =>
input.GetLines().ToList()
);
Resultado:
00: 00: 03.9603894
00: 00: 00.0029996
00: 00: 04.8221971
Ligeiramente torcido, mas um bloco iterador para fazer isso:
public static IEnumerable<string> Lines(this string Text)
{
int cIndex = 0;
int nIndex;
while ((nIndex = Text.IndexOf(Environment.NewLine, cIndex + 1)) != -1)
{
int sIndex = (cIndex == 0 ? 0 : cIndex + 1);
yield return Text.Substring(sIndex, nIndex - sIndex);
cIndex = nIndex;
}
yield return Text.Substring(cIndex + 1);
}
Você pode ligar para:
var result = input.Lines().ToArray();
private string[] GetLines(string text)
{
List<string> lines = new List<string>();
using (MemoryStream ms = new MemoryStream())
{
StreamWriter sw = new StreamWriter(ms);
sw.Write(text);
sw.Flush();
ms.Position = 0;
string line;
using (StreamReader sr = new StreamReader(ms))
{
while ((line = sr.ReadLine()) != null)
{
lines.Add(line);
}
}
sw.Close();
}
return lines.ToArray();
}
É complicado lidar adequadamente com terminações de linhas mistas . Como sabemos, os personagens linha de terminação pode ser "Line Feed" (ASCII 10, \n
, \x0A
, \u000A
), "Return" (ASCII 13, \r
, \x0D
, \u000D
), ou alguma combinação deles. Voltando ao DOS, o Windows usa a sequência de dois caracteres CR-LF \u000D\u000A
, portanto, essa combinação deve emitir apenas uma única linha. O Unix usa um único \u000A
Macs e muito antigos usam um único \u000D
caractere. A maneira padrão de tratar misturas arbitrárias desses caracteres em um único arquivo de texto é a seguinte:
\u000D\u000A
), esses dois juntos pulam apenas uma linha.String.Empty
é a única entrada que não retorna linhas (qualquer caractere implica pelo menos uma linha)A regra anterior descreve o comportamento de StringReader.ReadLine e funções relacionadas, e a função mostrada abaixo produz resultados idênticos. É uma função eficiente de quebra de linha de C # que implementa obedientemente essas diretrizes para manipular corretamente qualquer sequência ou combinação arbitrária de CR / LF. As linhas enumeradas não contêm caracteres CR / LF. Linhas vazias são preservadas e retornadas como String.Empty
.
/// <summary>
/// Enumerates the text lines from the string.
/// ⁃ Mixed CR-LF scenarios are handled correctly
/// ⁃ String.Empty is returned for each empty line
/// ⁃ No returned string ever contains CR or LF
/// </summary>
public static IEnumerable<String> Lines(this String s)
{
int j = 0, c, i;
char ch;
if ((c = s.Length) > 0)
do
{
for (i = j; (ch = s[j]) != '\r' && ch != '\n' && ++j < c;)
;
yield return s.Substring(i, j - i);
}
while (++j < c && (ch != '\r' || s[j] != '\n' || ++j < c));
}
Nota: Se você não se importa com a sobrecarga de criar uma StringReader
instância em cada chamada, pode usar o seguinte código C # 7 . Como observado, embora o exemplo acima possa ser um pouco mais eficiente, ambas as funções produzem exatamente os mesmos resultados.
public static IEnumerable<String> Lines(this String s)
{
using (var tr = new StringReader(s))
while (tr.ReadLine() is String L)
yield return L;
}