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 ToCharArraychiamata non necessaria .
Se vuoi dividere per uno \no \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? ( StringSplitOptionsparametro): utilizzare StringSplitOptions.Noneinvece.
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\ro \n\ncome singola interruzione di riga che non è corretto.
Hello\n\nworld\n\nun 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 |\rall'account per terminatori di linea (più vecchi) per Mac.
\rcome 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 \u000AMac molto vecchio e usa un solo \u000Dpersonaggio. 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 StringReaderun'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;
}