¿Cómo se divide una cadena de varias líneas en líneas?
Yo se de esta manera
var result = input.Split("\n\r".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
se ve un poco feo y pierde líneas vacías. ¿Hay una mejor solución?
¿Cómo se divide una cadena de varias líneas en líneas?
Yo se de esta manera
var result = input.Split("\n\r".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
se ve un poco feo y pierde líneas vacías. ¿Hay una mejor solución?
Respuestas:
Si se ve feo, simplemente elimine la ToCharArray
llamada innecesaria .
Si desea dividir entre uno \n
u otro \r
, tiene dos opciones:
Use una matriz literal, pero esto le dará líneas vacías para las terminaciones de línea estilo Windows \r\n
:
var result = text.Split(new [] { '\r', '\n' });
Use una expresión regular, como lo indica Bart:
var result = Regex.Split(text, "\r\n|\r|\n");
Si desea conservar las líneas vacías, ¿por qué le dice explícitamente a C # que las tire? ( StringSplitOptions
parámetro): use StringSplitOptions.None
en su lugar.
Environment.NewLine
es un no-go en lo que a mí respecta. De hecho, de todas las soluciones posibles, prefiero la que usa expresiones regulares, ya que solo eso maneja todas las plataformas de origen correctamente.
StringSplitOptions.RemoveEmptyEntries
.
using (StringReader sr = new StringReader(text)) {
string line;
while ((line = sr.ReadLine()) != null) {
// do something
}
}
string.Split
o Regex.Split
)?
Esto funciona muy bien y es más rápido que Regex:
input.Split(new[] {"\r\n", "\r", "\n"}, StringSplitOptions.None)
Es importante tener "\r\n"
primero en la matriz para que se tome como un salto de línea. Lo anterior da los mismos resultados que cualquiera de estas soluciones Regex:
Regex.Split(input, "\r\n|\r|\n")
Regex.Split(input, "\r?\n|\r")
Excepto que Regex resulta ser aproximadamente 10 veces más lento. Aquí está mi prueba:
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")
);
Salida:
00: 00: 03.8527616
00: 00: 31.8017726
00: 00: 32.5557128
y aquí está el método de extensión:
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
o \n\n
como un salto de línea único que no es correcto.
Hello\n\nworld\n\n
un caso extremo? Es claramente una línea con texto, seguida de una línea vacía, seguida de otra línea con texto, seguida de una línea vacía.
Puedes usar Regex.Split:
string[] tokens = Regex.Split(input, @"\r?\n|\r");
Editar: agregado |\r
a la cuenta para terminadores de línea Mac (más antiguos).
\r
como final de línea.
Si desea mantener líneas vacías, simplemente elimine las StringSplitOptions.
var result = input.Split(System.Environment.NewLine.ToCharArray());
Tuve esta otra respuesta, pero esta, basada en la respuesta de Jack , es significativamente más rápida , ya que funciona de forma asíncrona, aunque un poco más 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
Prueba:
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()
);
Salida:
00: 00: 03.9603894
00: 00: 00.0029996
00: 00: 04.8221971
Ligeramente torcido, pero un bloque iterador para hacerlo:
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);
}
Entonces puede llamar:
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();
}
Es complicado manejar correctamente las terminaciones de línea mixtas . Como sabemos, los caracteres de fin de línea pueden ser "salto de línea" (ASCII 10, \n
, \x0A
, \u000A
), "retorno de carro" (ASCII 13, \r
, \x0D
, \u000D
), o alguna combinación de ellos. Volviendo a DOS, Windows usa la secuencia de dos caracteres CR-LF \u000D\u000A
, por lo que esta combinación solo debería emitir una sola línea. Unix usa un solo \u000A
, y Macs muy antiguos usaban un solo \u000D
carácter. La forma estándar de tratar mezclas arbitrarias de estos caracteres dentro de un solo archivo de texto es la siguiente:
\u000D\u000A
) entonces estos dos juntos saltan solo una línea.String.Empty
es la única entrada que no devuelve líneas (cualquier carácter implica al menos una línea)La regla anterior describe el comportamiento de StringReader.ReadLine y funciones relacionadas, y la función que se muestra a continuación produce resultados idénticos. Es una función eficiente de salto de línea C # que implementa debidamente estas pautas para manejar correctamente cualquier secuencia arbitraria o combinación de CR / LF. Las líneas enumeradas no contienen ningún carácter CR / LF. Las líneas vacías se conservan y devuelven 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: Si no le importa la sobrecarga de crear una StringReader
instancia en cada llamada, puede usar el siguiente código C # 7 en su lugar. Como se señaló, aunque el ejemplo anterior puede ser un poco más eficiente, ambas funciones producen exactamente los mismos resultados.
public static IEnumerable<String> Lines(this String s)
{
using (var tr = new StringReader(s))
while (tr.ReadLine() is String L)
yield return L;
}