Pregunta

¿Cómo se divide cadena de múltiples líneas en líneas?

Sé que de esta manera

var result = input.Split("\n\r".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);

parece un poco feo y pierde líneas vacías. ¿Hay una solución mejor?

¿Fue útil?

Solución

  • Si se ve feo, basta con retirar la llamada ToCharArray innecesaria.

  • Si desea dividir por cualquiera \n o \r, tienes dos opciones:

    • Utilice un literal de matriz - pero esto le dará líneas vacías para la línea de estilo de Windows terminaciones \r\n:

      var result = text.Split(new [] { '\r', '\n' });
      
    • Utilice una expresión regular, como se indica por Bart:

      var result = Regex.Split(text, "\r\n|\r|\n");
      
  • Si desea conservar las líneas vacías, ¿por qué se le dice explícitamente C # para tirar a la basura? (Parámetro StringSplitOptions) - uso StringSplitOptions.None lugar

  • .

Otros consejos

using (StringReader sr = new StringReader(text)) {
    string line;
    while ((line = sr.ReadLine()) != null) {
        // do something
    }
}

Actualización: aquí para obtener una solución alternativa / asíncrono


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" por primera vez en la matriz de modo que se toma como una ruptura 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")

A excepción de 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á la 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

Se puede usar Regex.Split:

string[] tokens = Regex.Split(input, @"\r?\n|\r");

Edit: añadido |\r para dar cuenta de las terminaciones de línea Mac (mayores).

Si desea mantener las líneas vacías simplemente eliminar los StringSplitOptions.

var result = input.Split(System.Environment.NewLine.ToCharArray());

otra respuesta, pero éste, en base a Jack respuesta , es significativamente más rápido .

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 torcida, pero un bloque iterador 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);
}

A continuación, puede llamar a:

var result = input.Lines().ToArray();
      char[] archDelim = new char[] { '\r', '\n' };
      words = asset.text.Split(archDelim, StringSplitOptions.RemoveEmptyEntries); 
    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 difícil de manejar terminaciones de línea correctamente. 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 utiliza la secuencia de dos caracteres CR-LF \u000D\u000A, por lo que esta combinación sólo debe emitir una sola línea. Unix utiliza un solo \u000A, y muy viejo Mac utiliza un solo carácter \u000D. La manera estándar para el tratamiento de mezclas arbitrarias de estos caracteres dentro de un único archivo de texto es como sigue:

  • todos y cada uno CR o de salto debe pasar a la siguiente línea SALVO ...
  • ... si un CR es seguida inmediatamente por LF (\u000D\u000A), entonces estos dos juntos omitir una sola línea.
  • String.Empty es la única entrada que no devuelve ningún líneas (cualquier carácter implica al menos una línea)
  • La última línea debe ser devuelto incluso si no tiene ninguna CR ni LF.

La regla anterior describe el comportamiento de StringReader. ReadLine y las funciones relacionadas, y la función se muestra a continuación produce resultados idénticos. Es un C # línea romper función eficiente que diligentemente implementa estas instrucciones para manejar correctamente cualquier secuencia arbitraria o combinación de CR / LF. Las líneas enumeradas no contienen caracteres 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 te importa la sobrecarga de crear una instancia de StringReader en cada llamada, puede utilizar el siguiente C # 7 código en su lugar. Como se ha indicado, mientras que el ejemplo anterior puede ser ligeramente más eficiente, ambas de estas funciones producen los mismos resultados exactos.

public static IEnumerable<String> Lines(this String s)
{
    using (var tr = new StringReader(s))
        while (tr.ReadLine() is String L)
            yield return L;
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top