Domanda

Come si fa a dividere stringa multi linea in linee?

So che in questo modo

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

sembra un po 'brutto e perde righe vuote. C'è una soluzione migliore?

È stato utile?

Soluzione

  • Se sembra brutto, è sufficiente rimuovere la chiamata ToCharArray superfluo.

  • Se si desidera dividere da una \n o \r, hai due opzioni:

    • Utilizzare un array letterale - ma questo vi darà le linee vuote per la linea stile Windows terminazioni \r\n:

      var result = text.Split(new [] { '\r', '\n' });
      
    • Con un'espressione regolare, come indicato da Bart:

      var result = Regex.Split(text, "\r\n|\r|\n");
      
  • Se si desidera conservare le linee vuote, perché si fa a dire in modo esplicito C # per buttarli via? (Parametro StringSplitOptions) - uso StringSplitOptions.None invece

  • .

Altri suggerimenti

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

Aggiornamento:. Vedere qui per una soluzione alternativa / asincrona


Questa grande opera ed è più veloce di Regex:

input.Split(new[] {"\r\n", "\r", "\n"}, StringSplitOptions.None)

E 'importante avere "\r\n" prima nella matrice in modo che è preso come un'interruzione di riga. Quanto sopra dà gli stessi risultati di una di queste soluzioni Regex:

Regex.Split(input, "\r\n|\r|\n")

Regex.Split(input, "\r?\n|\r")

Se non fosse che Regex risulta essere circa 10 volte più lento. Ecco la mia prova:

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")
);

Output:

00: 00: 03,8527616

00: 00: 31,8,017726 millions

00: 00: 32,5,557128 millions

ed ecco la Metodo di estensione:

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);
    }
}

Utilizzo:

input.GetLines()      // keeps empty lines

input.GetLines(true)  // removes empty lines

È possibile utilizzare Regex.Split:

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

Edit: aggiunte |\r per tenere conto di (vecchie) Mac terminatori di linea.

Se si desidera mantenere le linee vuote è sufficiente rimuovere la StringSplitOptions.

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

Ho avuto questa altra risposta ma questo, sulla base di Jack risposta , è significativamente più veloce potrebbe essere preferito dal momento che funziona in modo asincrono, anche se leggermente più lento.

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;
            }
        }
    }
}

Utilizzo:

input.GetLines()      // keeps empty lines

input.GetLines(true)  // removes empty lines

Prova:

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()
);

Output:

00: 00: 03,9603894

00: 00: 00,0029996

00: 00: 04,8221971

Un po 'contorto, ma un blocco iteratore per farlo:

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);
}

È quindi possibile chiamare:

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

E 'difficile da gestire misti finali di linea correttamente. Come sappiamo, i caratteri di terminazione linea possono essere "Line Feed" (ASCII 10, \n, \x0A, \u000A), "Carriage Return" (ASCII 13, \r, \x0D, \u000D), o una combinazione di questi. Tornando al DOS, Windows utilizza la sequenza di due caratteri CR-LF \u000D\u000A, quindi questa associazione deve emettere solo una singola linea. Unix utilizza un unico \u000A, e molto vecchio Mac usato un singolo carattere \u000D. Il metodo standard per il trattamento di miscele arbitrarie di questi personaggi all'interno di un singolo file di testo è il seguente:

  • ogni CR o il carattere LF dovrebbe passare alla riga successiva TRANNE ...
  • ... se un CR è immediatamente seguito da LF (\u000D\u000A) allora questi due insieme saltare una sola riga.
  • String.Empty è l'unico input che restituisce nessuna riga (carattere comporta almeno una linea)
  • L'ultima riga deve essere restituito anche se non ha né CR né LF.

La regola precedente descrive il comportamento di StringReader. ReadLine e funzioni correlate, e la funzione mostrato sotto produce risultati identici. È un efficiente C # linea di rottura funzione che implementa diligentemente queste linee guida per gestire correttamente qualsiasi sequenza arbitraria o combinazione di CR / LF. Le linee enumerati non contengono alcun carattere CR / LF. Le righe vuote sono conservati e restituiti come 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 non ti dispiace il sovraccarico di creare un'istanza di StringReader su ogni chiamata, è possibile utilizzare il seguente C # 7 codice invece. Come notato, mentre l'esempio di cui sopra può essere leggermente più efficiente, entrambe queste funzioni producono lo stesso risultato.

public static IEnumerable<String> Lines(this String s)
{
    using (var tr = new StringReader(s))
        while (tr.ReadLine() is String L)
            yield return L;
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top