Come si divide la stringa multi-linea in linee?
Lo so così
var result = input.Split("\n\r".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
sembra un po 'brutto e perde le righe vuote. C'è una soluzione migliore?
Come si divide la stringa multi-linea in linee?
Lo so così
var result = input.Split("\n\r".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
sembra un po 'brutto e perde le righe vuote. C'è una soluzione migliore?
Risposte:
Se sembra brutto, basta rimuovere la ToCharArray
chiamata non necessaria .
Se vuoi dividere per uno \n
o \r
, hai due opzioni:
Usa un array letterale, ma questo ti darà linee vuote per i finali di linea in stile Windows \r\n
:
var result = text.Split(new [] { '\r', '\n' });
Usa un'espressione regolare, come indicato da Bart:
var result = Regex.Split(text, "\r\n|\r|\n");
Se vuoi conservare le righe vuote, perché dici esplicitamente a C # di buttarle via? ( StringSplitOptions
parametro): utilizzare StringSplitOptions.None
invece.
Environment.NewLine
è un no-go per quanto mi riguarda. In effetti, tra tutte le possibili soluzioni preferisco quella che utilizza espressioni regolari poiché solo quella gestisce correttamente tutte le piattaforme di origine.
StringSplitOptions.RemoveEmptyEntries
.
Funziona alla grande ed è più veloce di Regex:
input.Split(new[] {"\r\n", "\r", "\n"}, StringSplitOptions.None)
È importante avere "\r\n"
prima nell'array in modo che venga preso come un'interruzione di riga. Quanto sopra fornisce gli stessi risultati di una di queste soluzioni Regex:
Regex.Split(input, "\r\n|\r|\n")
Regex.Split(input, "\r?\n|\r")
Solo che Regex risulta essere circa 10 volte più lento. Ecco il mio test:
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")
);
Produzione:
00: 00: 03,8527616
00: 00: 31,8,017726 millions
00: 00: 32,5,557128 millions
ed ecco il 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);
}
}
Uso:
input.GetLines() // keeps empty lines
input.GetLines(true) // removes empty lines
[\r\n]{1,2}
\n\r
o \n\n
come singola interruzione di riga che non è corretto.
Hello\n\nworld\n\n
un caso limite? È chiaramente una riga con testo, seguita da una riga vuota, seguita da un'altra riga con testo, seguita da una riga vuota.
È possibile utilizzare Regex.Split:
string[] tokens = Regex.Split(input, @"\r?\n|\r");
Modifica: aggiunto |\r
all'account per terminatori di linea (più vecchi) per Mac.
\r
come fine riga.
Se vuoi mantenere le righe vuote basta rimuovere StringSplitOptions.
var result = input.Split(System.Environment.NewLine.ToCharArray());
Ho avuto quest'altra risposta, ma questa, basata sulla risposta di Jack , è significativamente più veloce, si potrebbe preferire poiché 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;
}
}
}
}
Uso:
input.GetLines() // keeps empty lines
input.GetLines(true) // removes empty lines
Test:
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()
);
Produzione:
00: 00: 03,9603894
00: 00: 00,0029996
00: 00: 04,8221971
Leggermente 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();
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();
}
È difficile gestire correttamente le terminazioni di linee miste . 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 combinazione dovrebbe emettere una sola riga. Unix usa un singolo \u000A
Mac molto vecchio e usa un solo \u000D
personaggio. Il modo standard di trattare miscele arbitrarie di questi caratteri all'interno di un singolo file di testo è il seguente:
\u000D\u000A
), questi due saltano insieme solo una riga.String.Empty
è l'unico input che non restituisce righe (qualsiasi carattere comporta almeno una riga)La regola precedente descrive il comportamento di StringReader.ReadLine e le funzioni correlate e la funzione mostrata di seguito produce risultati identici. È un'efficace funzione di interruzione della linea C # che implementa debitamente queste linee guida per gestire correttamente qualsiasi sequenza o combinazione arbitraria di CR / LF. Le righe elencate non contengono alcun carattere CR / LF. Le righe vuote vengono conservate e restituite 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 StringReader
un'istanza per ogni chiamata, puoi invece usare il seguente codice C # 7 . Come notato, mentre l'esempio sopra può essere leggermente più efficiente, entrambe queste funzioni producono esattamente gli stessi risultati.
public static IEnumerable<String> Lines(this String s)
{
using (var tr = new StringReader(s))
while (tr.ReadLine() is String L)
yield return L;
}